简单工厂方法模式

1。实现接口编程,隔离实现,不再对外暴露接口的实现,接口的实现转交给工厂方法。

 

简单工厂


        简单工厂不是一个标准的设计模式,但是它实在是太常用了,简单而又神奇,所以还是需要好好掌握的,就当是对学习设计模式的热身运动吧。
        为了保持一致性,我们尽量按照学习其它模式的步骤来进行学习。


1  场景问题

        大家都知道,在Java应用开发中,要“面向接口编程”。
        那么什么是接口?接口有什么作用?接口如何使用?一起来回顾一下:


1.1  接口回顾

(1)Java中接口的概念
        在Java中接口是一种特殊的抽象类,跟一般的抽象类相比,接口里面的所有方法都是抽象方法,接口里面的所有属性都是常量。也就是说,接口里面是只有方法定义而不会有任何方法实现。
(2)接口用来干什么
        通常用接口来定义实现类的外观,也就是实现类的行为定义,用来约束实现类的行为。接口就相当于一份契约,根据外部应用需要的功能,约定了实现类应该要实现的功能,但是具体的实现类除了实现接口约定的功能外,还可以根据需要实现一些其它的功能,这是允许的,也就是说实现类的功能包含但不仅限于接口约束的功能。
        通过使用接口,可以实现不相关类的相同行为,而不需考虑这些类之间的层次关系,接口就是实现类对外的外观。
(3)接口的思想
        根据接口的作用和用途,浓缩下来,接口的思想就是“封装隔离”。
        通常提到封装是指对数据的封装,但是这里的封装是指“对被隔离体的行为的封装”,或者是“对被隔离体的职责的封装”;而隔离指的是外部调用和内部实现,外部调用只能通过接口进行调用,而外部调用是不知道内部具体实现的,也就是说外部调用和内部实现是被接口隔离开的。
(4)使用接口的好处
        由于外部调用和内部实现被接口隔离开了,那么只要接口不变,内部实现的变化就不会影响到外部应用,从而使得系统更灵活,具有更好的扩展性和可维护性,这也就是所谓“接口是系统可插拔性的保证”这句话的意思。
(5)接口和抽象类的选择
        既然接口是一种特殊的抽象类,那么在开发中,何时选用接口,何时选用抽象类呢?
        对于它们的选择,在开发中是一个很重要的问题,特别总结两句话给大家:

  • 优先选用接口
  • 在如下情况应选择抽象类:既要定义子类的行为,又要为子类提供公共的功能

1.2  面向接口编程

        面向接口编程是Java编程中的一个重要原则。
        在Java 程序设计里面,非常讲究层的划分和模块的划分。通常按照三层来划分Java程序,分别是表现层、逻辑层、数据层,它们之间都要通过接口来通讯。
        在每一个层里面,又有很多个小模块,一个小模块对外也应该是一个整体,那么一个模块对外也应该提供接口,其它地方需要使用到这个模块的功能,都应该通过此接口来进行调用。这也就是常说的“接口是被其隔离部分的外观”。基本的三层结构如图1所示:

 
简单工厂方法模式
 图1  基本的三层结构示意图

在一个层内部的各个模块交互也要通过接口,如图2所示:

 
简单工厂方法模式
 

 图2  一个层内部的各个模块交互示意图


        各个部分的接口具体应该如何去定义,具体的内容是什么,不去深究,那是需要具体问题具体分析的,这里只是来学习设计的方法。
        上面频频提到“组件”,那么什么是组件呢?先简单的名词解释一下:
        所谓组件:从设计上讲,组件就是能完成一定功能的封装体。小到一个类,大到一个系统,都可以称为组件,因为一个小系统放到更大的系统里面去,也就当个组件而已。事实上,从设计的角度看,系统、子系统、模块、组件等说的其实是同一回事情,都是完成一定功能的封装体,只不过功能多少不同而已。
        继续刚才的思路,大家会发现,不管是一层还是一个模块或者一个组件,都是一个被接口隔离的整体,那么下面我们就不去区分它们,统一认为都是接口隔离体即可,如图3所示:

 
简单工厂方法模式
图3  接口隔离体示意图


 既然在Java中需要面向接口编程,那么在程序中到底如何使用接口,来做到真正的面向接口编程呢?

 1.3  不用模式的解决方案

        回忆一下,以前是如何使用接口的呢,假设有一个接口叫Api,然后有一个实现类Impl实现了它,在客户端怎么用这个接口呢?
        通常都是在客户端创建一个Impl的实例,把它赋值给一个Api接口类型的变量,然后客户端就可以通过这个变量来操作接口的功能了,此时具体的结构图如图4:

 

 
简单工厂方法模式
 图4  基本的接口和实现

还是用代码来说明,会更清楚一些。

(1)先定义接口Api,示例代码如下:

Java代码 简单工厂方法模式
  1. /**  
  2. * 某个接口(通用的、抽象的、非具体的功能)   
  3. */  
  4. public interface Api {   
  5.     /**  
  6.      * 某个具体的功能方法的定义,用test1来演示一下。  
  7.      * 这里的功能很简单,把传入的s打印输出即可   
  8.      * @param s 任意想要打印输出的字符串  
  9.      */  
  10.     public void test1(String s);   
  11. }  
/**
* 某个接口(通用的、抽象的、非具体的功能) 
*/
public interface Api {
	/**
	 * 某个具体的功能方法的定义,用test1来演示一下。
	 * 这里的功能很简单,把传入的s打印输出即可 
	 * @param s 任意想要打印输出的字符串
	 */
	public void test1(String s);
}

 
(2)既然有了接口,自然就要有实现,定义实现Impl,示例代码如下:

Java代码 简单工厂方法模式
  1. /**  
  2. * 对接口的实现   
  3. */  
  4. public class Impl implements Api{   
  5.     public void test1(String s) {   
  6.         System.out.println("Now In Impl. The input s=="+s);   
  7.     }   
  8. }  
/**
* 对接口的实现 
*/
public class Impl implements Api{
	public void test1(String s) {
		System.out.println("Now In Impl. The input s=="+s);
	}
}

 

(3)那么此时的客户端怎么写呢?
按照Java的知识,接口不能直接使用,需要使用接口的实现类,示例代码如下:

Java代码 简单工厂方法模式
  1. /**  
  2. * 客户端:测试使用Api接口  
  3. */  
  4. public class Client {   
  5.     public static void main(String[] args) {   
  6.         Api api = new Impl();   
  7.         api.test1("哈哈,不要紧张,只是个测试而已!");   
  8.     }   
  9. }  
/**
* 客户端:测试使用Api接口
*/
public class Client {
	public static void main(String[] args) {
		Api api = new Impl();
		api.test1("哈哈,不要紧张,只是个测试而已!");
	}
}

 

 

1.4  有何问题

        上面写得没错吧,在Java的基础知识里面就是这么学的,难道这有什么问题吗?
        请仔细看位于客户端的下面这句话:

Java代码 简单工厂方法模式
  1. Api api = new Impl();  
	Api api = new Impl();

        然后再想想接口的功能和思想,发现什么了?仔细再想想?

 

        你会发现在客户端调用的时候,客户端不但知道了接口,同时还知道了具体的实现就是Impl。而接口的思想是“封装隔离”,而Impl这个实现类,应该是被接口Api封装并同客户端隔离开的,也就是说,客户端根本就不应该知道具体的实现类是Impl。
        有朋友说,那好,我就把Impl从客户端拿掉,让Api真正的对实现进行“封装隔离”,然后我们还是面向接口来编程。可是,新的问题出现了,当他把“new Impl()”去掉过后,发现他无法得到Api接口对象了,怎么办呢?
        把这个问题描述一下:在Java编程中,出现只知接口而不知实现,该怎么办?
        就像现在的Client,它知道要使用Api接口,但是不知由谁实现,也不知道如何实现,从而得不到接口对象,就无法使用接口,该怎么办呢?

2  解决方案


1  简单工厂来解决

        用来解决上述问题的一个合理的解决方案就是简单工厂,那么什么是简单工厂呢?
1:简单工厂定义

简单工厂方法模式
 

2:应用简单工厂来解决的思路
        分析上面的问题,虽然不能让模块外部知道模块内的具体实现,但是模块内部是可以知道实现类的,而且创建接口是需要具体实现类的。
        那么干脆在模块内部新建一个类,在这个类里面来创建接口,然后把创建好的接口返回给客户端,这样外部应用就只需要根据这个类来获取相应的接口对象,然后就可以操作接口定义的方法了。把这样的对象称为简单工厂,就叫Factory吧。
        这样一来,客户端就可以通过这个Factory来获取需要的接口对象,然后调用接口的方法来实现需要的功能,而且客户端也不用再关心具体实现了。


2.2  简单工厂结构和说明

        简单工厂的结构如图5所示:

 
简单工厂方法模式
 Api:
        定义客户所需要的功能接口
Impl:
       具体实现Api的实现类,可能会有多个
Factory:
       工厂,选择合适的实现类来创建Api接口对象
Client:
       客户端,通过Factory去获取Api接口对象,然后面向Api接口编程


2.3  简单工厂示例代码

(1)先看看Api的定义,示例代码如下:

Java代码 简单工厂方法模式
  1. /**  
  2.  * 接口的定义,该接口可以通过简单工厂来创建  
  3.  */  
  4. public interface Api {   
  5.     /**  
  6.      * 示意,具体的功能方法的定义  
  7.      * @param s 示意,需要的参数  
  8.      */  
  9.     public void operation(String s);   
  10. }  
/**
 * 接口的定义,该接口可以通过简单工厂来创建
 */
public interface Api {
	/**
	 * 示意,具体的功能方法的定义
	 * @param s 示意,需要的参数
	 */
	public void operation(String s);
}

 (2)定义了接口,该来实现它了,ImplA的示例代码如下:

Java代码 简单工厂方法模式
  1. /**  
  2.  * 接口的具体实现对象A   
  3.  */  
  4. public class ImplA implements Api{   
  5.     public void operation(String s) {   
  6.         //实现功能的代码,示意一下   
  7.         System.out.println("ImplA s=="+s);   
  8.     }   
  9. }  
/**
 * 接口的具体实现对象A 
 */
public class ImplA implements Api{
	public void operation(String s) {
		//实现功能的代码,示意一下
		System.out.println("ImplA s=="+s);
	}
}

 ImplB的示意实现和ImplA基本一样,示例代码如下:

Java代码 简单工厂方法模式
  1. /**  
  2.  * 接口的具体实现对象B   
  3.  */  
  4. public class ImplB implements Api{   
  5.     public void operation(String s) {   
  6.         //实现功能的代码,示意一下   
  7.         System.out.println("ImplB s=="+s);   
  8.     }   
  9. }  
/**
 * 接口的具体实现对象B 
 */
public class ImplB implements Api{
	public void operation(String s) {
		//实现功能的代码,示意一下
		System.out.println("ImplB s=="+s);
	}
}

 (3)该来看看简单工厂的实现,示例代码如下:

Java代码 简单工厂方法模式
  1. /**  
  2.  * 工厂类,用来创造Api对象  
  3.  */  
  4. public class Factory {   
  5.     /**  
  6.      * 具体的创造Api对象的方法  
  7.      * @param condition 示意,从外部传入的选择条件  
  8.      * @return 创造好的Api对象  
  9.      */  
  10.     public static Api createApi(int condition){   
  11.         //应该根据某些条件去选择究竟创建哪一个具体的实现对象,   
  12.         //这些条件可以从外部传入,也可以从其它途径获取。   
  13.         //如果只有一个实现,可以省略条件,因为没有选择的必要。   
  14.   
  15.         //示意使用条件   
  16.         Api api = null;   
  17.         if(condition == 1){   
  18.             api = new ImplA();   
  19.         }else if(condition == 2){   
  20.             api = new ImplB();   
  21.         }   
  22.         return api;   
  23.     }   
  24. }  
/**
 * 工厂类,用来创造Api对象
 */
public class Factory {
	/**
	 * 具体的创造Api对象的方法
	 * @param condition 示意,从外部传入的选择条件
	 * @return 创造好的Api对象
	 */
	public static Api createApi(int condition){
		//应该根据某些条件去选择究竟创建哪一个具体的实现对象,
		//这些条件可以从外部传入,也可以从其它途径获取。
		//如果只有一个实现,可以省略条件,因为没有选择的必要。

		//示意使用条件
		Api api = null;
		if(condition == 1){
			api = new ImplA();
		}else if(condition == 2){
			api = new ImplB();
		}
		return api;
	}
}

 (4)再来看看客户端的示意,示例代码如下:

Java代码 简单工厂方法模式
  1. /**  
  2.  * 客户端,使用Api接口  
  3.  */  
  4. public class Client {   
  5.     public static void main(String[] args) {   
  6.         //通过简单工厂来获取接口对象   
  7.         Api api = Factory.createApi(1);   
  8.         api.operation("正在使用简单工厂");   
  9.     }   
  10. }  
/**
 * 客户端,使用Api接口
 */
public class Client {
	public static void main(String[] args) {
		//通过简单工厂来获取接口对象
		Api api = Factory.createApi(1);
		api.operation("正在使用简单工厂");
	}
}

 

2.4  使用简单工厂重写示例 

        要使用简单工厂来重写前面的示例,主要就是要创建一个简单工厂对象,让简单工厂来负责创建接口对象。然后让客户端通过工厂来获取接口对象,而不再由客户端自己去创建接口的对象了。
        此时系统的结构如图6所示。

 
简单工厂方法模式

                      图6  使用简单工厂重写示例的结构示意图

 (1)接口Api和实现类Impl都和前面的示例一样,就不去赘述了。
(2)新创建一个简单工厂的对象,示例代码如下:

Java代码 简单工厂方法模式
  1. /**  
  2.  * 工厂类,用来创造Api对象  
  3.  */  
  4. public class Factory {   
  5.     /**  
  6.      * 具体的创造Api对象的方法  
  7.      * @return 创造好的Api对象  
  8.      */  
  9.     public static Api createApi(){   
  10.         //由于只有一个实现,就不用条件判断了   
  11.         return new Impl();   
  12.     }   
  13. }  
/**
 * 工厂类,用来创造Api对象
 */
public class Factory {
	/**
	 * 具体的创造Api对象的方法
	 * @return 创造好的Api对象
	 */
	public static Api createApi(){
		//由于只有一个实现,就不用条件判断了
		return new Impl();
	}
}

 

(3)使用简单工厂
         客户端如何使用简单工厂提供的功能呢?这个时候,客户端就不用再自己去创建接口的对象了,应该使用工厂来获取,经过改造,客户端代码如下:

Java代码 简单工厂方法模式
  1. /**  
  2. * 客户端:测试使用Api接口  
  3. */  
  4. public class Client {   
  5.     public static void main(String[] args) {   
  6.         //重要改变,没有new Impl()了,取而代之Factory.createApi()   
  7. Api api = Factory.createApi();   
  8.         api.test1("哈哈,不要紧张,只是个测试而已!");   
  9.     }   
  10. }  
/**
* 客户端:测试使用Api接口
*/
public class Client {
	public static void main(String[] args) {
		//重要改变,没有new Impl()了,取而代之Factory.createApi()
Api api = Factory.createApi();
		api.test1("哈哈,不要紧张,只是个测试而已!");
	}
}

 

         就如同上面的示例,客户端通过简单工厂创建了一个实现接口的对象,然后面向接口编程,从客户端来看,它根本就不知道具体的实现是什么,也不知道是如何实现的,它只知道通过工厂获得了一个接口对象,然后就能通过这个接口来获取想要的功能。
         事实上,简单工厂能帮助我们真正开始面向接口编程,像以前的做法,其实只是用到了接口的多态那部分的功能,最重要的“封装隔离性”并没有体现出来。

3.1  典型疑问

        首先来解决一个常见的疑问:可能有朋友会认为,上面示例中的简单工厂看起来不就是把客户端里面的“new Impl()”移动到简单工厂里面吗?不还是一样通过new一个实现类来得到接口吗?把“new Impl()”这句话放到客户端和放到简单工厂里面有什么不同吗?
        理解这个问题的重点就在于理解简单工厂所处的位置。
        根据前面的学习,我们知道接口是用来封装隔离具体的实现的,目标就是不要让客户端知道封装体内部的具体实现。简单工厂的位置是位于封装体内的,也就是简单工厂是跟接口和具体的实现在一起的,算是封装体内部的一个类,所以简单工厂知道具体的实现类是没有关系的。整理一下简单工厂的结构图,新的图如图7所示:

简单工厂方法模式
                                        图7  整理后的简单工厂结构
       

        图7中虚线框,就好比是一个组件的包装边界,表示接口、实现类和工厂类组合成了一个组件,在这个封装体里面,只有接口和工厂是对外的,也就是让外部知道并使用的,所以故意漏了一些在虚线框外,而具体的实现类是不对外的,被完全包含在虚线框内。
        对于客户端而言,只是知道了接口Api和简单工厂Factory,通过Factory就可以获得Api了,这样就达到了让Client在不知道具体实现类的情况下获取接口Api。
        所以看似简单的把“new Impl()”这句话从客户端里面移动到了简单工厂里面,其实是有了质的变化的。


3.2  认识简单工厂

(1)简单工厂的功能
        工厂嘛,就是用来造东西的。在Java里面,通常情况下是用来造接口的,但是也可以造抽象类,甚至是一个具体的类实例。
         一定要注意,虽然前面的示例是利用简单工厂来创建的接口,但是也是可以用简单工厂来创建抽象类或者是普通类的实例的。
(2)静态工厂
        使用简单工厂的时候,通常不用创建简单工厂类的类实例,没有创建实例的必要。因此可以把简单工厂类实现成一个工具类,直接使用静态方法就可以了,也就是说简单工厂的方法通常都是静态的,所以也被称为静态工厂。如果要防止客户端无谓的创造简单工厂实例,还可以把简单工厂的构造方法私有化了。
(3)万能工厂
        一个简单工厂可以包含很多用来构造东西的方法,这些方法可以创造不同的接口、抽象类或者是类实例,一个简单工厂理论上可以构造任何东西,所以又称之为“万能工厂”。
       虽然上面的实例中,在简单工厂里面只有一个方法,但事实上,是可以有很多这样创建方法的,这点要注意。
(4)简单工厂创建对象的范围
       虽然从理论上讲,简单工厂什么都能造,但对于简单工厂可创建对象的范围,通常不要太大,建议控制在一个独立的组件级别或者一个模块级别,也就是一个组件或模块一个简单工厂。否则这个简单工厂类会职责不明,有点大杂烩的感觉。
(5)简单工厂的调用顺序示意图
       简单工厂的调用顺序如图8所示:

简单工厂方法模式
 图8  简单工厂的调用顺序示意图


(6)简单工厂命名的建议

 

  • 类名建议为“模块名称+Factory”,比如:用户模块的工厂就称为:UserFactory
  • 方法名称通常为“get+接口名称”或者是“create+接口名称”,比如:有一个接口名称为UserEbi,那么方法名称通常为:getUserEbi 或者是 createUserEbi。
  •  当然,也有一些朋友习惯于把方法名称命名为“new+接口名称”,比如:newUserEbi,我们不是很建议。因为new在Java中代表特定的含义,而且通过简单工厂的方法来获取对象实例,并不一定每次都是要new一个新的实例。如果使用newUserEbi,这会给人错觉,好像每次都是new一个新的实例一样。

 

3.3  简单工厂中方法的写法

 

        虽然说简单工厂的方法多是用来造接口的,但是仔细分析就会发现,真正能实现功能的是具体的实现类,这些实现类是已经做好的,并不是真的靠简单工厂来创造出来的,简单工厂的方法无外乎就是:实现了选择一个合适的实现类来使用。
        所以简单工厂方法的内部主要实现的功能是“选择合适的实现类”来创建实例对象。既然要实现选择,那么就需要选择的条件或者是选择的参数,选择条件或者是参数的来源通常又有几种:

  • 来源于客户端,由Client来传入参数
  • 来源于配置文件,从配置文件获取用于判断的值
  • 来源于程序运行期的某个值,比如从缓存中获取某个运行期的值

         下面来看个示例,看看由客户端来传入参数,如何写简单工厂中的方法。
(1)在刚才的示例上再添加一个实现,称为Impl2,示例代码如下:

Java代码 简单工厂方法模式
  1. /**  
  2.  * 对接口的一种实现   
  3.  */  
  4. public class Impl2 implements Api{   
  5.     public void test1(String s) {   
  6.         System.out.println("Now In Impl The input s=="+s);   
  7.     }   
  8. }  
/**
 * 对接口的一种实现 
 */
public class Impl2 implements Api{
	public void test1(String s) {
		System.out.println("Now In Impl The input s=="+s);
	}
}

 (2)现在对Api这个接口,有了两种实现,那么工厂类该怎么办呢?到底如何选择呢?不可能两个同时使用吧,看看新的工厂类,示例代码如下:

Java代码 简单工厂方法模式
  1. /**  
  2.  * 工厂类,用来创造Api的  
  3.  */  
  4. public class Factory {   
  5.     /**  
  6.      * 具体的创造Api的方法,根据客户端的参数来创建接口  
  7.      * @param type 客户端传入的选择创造接口的条件  
  8.      * @return 创造好的Api对象  
  9.      */  
  10.     public static Api createApi(int type){   
  11.         //这里的type也可以不由外部传入,而是直接读取配置文件来获取   
  12.         //为了把注意力放在模式本身上,这里就不去写读取配置文件的代码了   
  13.        
  14.         //根据type来进行选择,当然这里的1和2应该做成常量   
  15.         Api api = null;   
  16.         if(type==1){   
  17.             api = new Impl();   
  18.         }else if(type==2){   
  19.             api = new Impl2();   
  20.         }   
  21.         return api;   
  22.     }   
  23. }  
/**
 * 工厂类,用来创造Api的
 */
public class Factory {
	/**
	 * 具体的创造Api的方法,根据客户端的参数来创建接口
	 * @param type 客户端传入的选择创造接口的条件
	 * @return 创造好的Api对象
	 */
	public static Api createApi(int type){
		//这里的type也可以不由外部传入,而是直接读取配置文件来获取
		//为了把注意力放在模式本身上,这里就不去写读取配置文件的代码了
	
		//根据type来进行选择,当然这里的1和2应该做成常量
		Api api = null;
		if(type==1){
			api = new Impl();
		}else if(type==2){
			api = new Impl2();
		}
		return api;
	}
}

 (3)客户端没有什么变化,只是在调用Factory的createApi方法的时候需要传入参数,示例代码如下:

Java代码 简单工厂方法模式
  1. public class Client {   
  2.     public static void main(String[] args) {   
  3.         //注意这里传递的参数,修改参数就可以修改行为,试试看吧   
  4.         Api api = Factory.createApi(2);   
  5.         api.test1("哈哈,不要紧张,只是个测试而已!");   
  6.     }   
  7. }  
public class Client {
	public static void main(String[] args) {
		//注意这里传递的参数,修改参数就可以修改行为,试试看吧
		Api api = Factory.createApi(2);
		api.test1("哈哈,不要紧张,只是个测试而已!");
	}
}

 (4)要注意这种方法有一个缺点
由于是从客户端在调用工厂的时候,传入选择的参数,这就说明客户端必须知道每个参数的含义,也需要理解每个参数对应的功能处理。这就要求必须在一定程度上,向客户暴露一定的内部实现细节。


3.4  可配置的简单工厂

        现在已经学会通过简单工厂来选择具体的实现类了,可是还有问题。比如:在现在的实现中,再新增加一种实现,会怎样呢?
        那就需要修改工厂类,才能把新的实现添加到现有系统中。比如现在新加了一个实现Impl3,那么需要类似下面这样来修改工厂类:

Java代码 简单工厂方法模式
  1. public class Factory {   
  2.     public static Api createApi(int type){   
  3.         Api api = null;   
  4.         if(type==1){   
  5.             api = new Impl();   
  6.         }else if(type==2){   
  7.             api = new Impl2();   
  8.         }   
  9.   
  10.         else if(type==3){   
  11.             api = new Impl3();   
  12.         }   
  13.         return api;   
  14.     }   
  15. }  
public class Factory {
	public static Api createApi(int type){
		Api api = null;
		if(type==1){
			api = new Impl();
		}else if(type==2){
			api = new Impl2();
		}

		else if(type==3){
			api = new Impl3();
		}
		return api;
	}
}

        每次新增加一个实现类都来修改工厂类的实现,肯定不是一个好的实现方式。那么现在希望新增加了实现类过后不修改工厂类,该怎么办呢?
         一个解决的方法就是使用配置文件,当有了新的实现类过后,只要在配置文件里面配置上新的实现类就好了,在简单工厂的方法里面可以使用反射,当然也可以使用IoC/DI(控制反转/依赖注入,这个不在这里讨论)来实现。
         看看如何使用反射加上配置文件,来实现添加新的实现类过后,无须修改代码,就能把这个新的实现类加入应用中。
(1)配置文件用最简单的properties文件,实际开发中多是xml配置。定义一个名称为“FactoryTest.properties”的配置文件,放置到Factory同一个包下面,内容如下:

Java代码 简单工厂方法模式
  1. ImplClass=cn.javass.dp.simplefactory.example5.Impl  
ImplClass=cn.javass.dp.simplefactory.example5.Impl

 如果新添加了实现类,修改这里的配置就可以了,就不需要修改程序了。
(2)此时的工厂类实现如下:

Java代码 简单工厂方法模式
  1. /**  
  2. * 工厂类,用来创造Api对象  
  3. */  
  4. public class Factory {   
  5.     /**  
  6.      * 具体的创造Api的方法,根据配置文件的参数来创建接口  
  7.      * @return 创造好的Api对象  
  8.      */  
  9.     public static Api createApi(){   
  10.         //直接读取配置文件来获取需要创建实例的类   
  11.         //至于如何读取Properties,还有如何反射这里就不解释了   
  12.         Properties p = new Properties();    
  13.         InputStream in = null;   
  14.         try {   
  15.             in = Factory.class.getResourceAsStream(   
  16. "FactoryTest.properties");   
  17.             p.load(in);   
  18.         } catch (IOException e) {   
  19.             System.out.println(   
  20. "装载工厂配置文件出错了,具体的堆栈信息如下:");   
  21.             e.printStackTrace();   
  22.         }finally{   
  23.             try {   
  24.                 in.close();   
  25.             } catch (IOException e) {   
  26.                 e.printStackTrace();   
  27.             }   
  28.         }   
  29.         //用反射去创建,那些例外处理等完善的工作这里就不做了   
  30.         Api api = null;   
  31.         try {   
  32.             api = (Api)Class.forName(p.getProperty("ImplClass"))   
  33. .newInstance();   
  34.         } catch (InstantiationException e) {   
  35.             e.printStackTrace();   
  36.         } catch (IllegalAccessException e) {   
  37.             e.printStackTrace();   
  38.         } catch (ClassNotFoundException e) {   
  39.             e.printStackTrace();   
  40.         }   
  41.         return api;   
  42.     }   
  43. }  
/**
* 工厂类,用来创造Api对象
*/
public class Factory {
	/**
	 * 具体的创造Api的方法,根据配置文件的参数来创建接口
	 * @return 创造好的Api对象
	 */
	public static Api createApi(){
		//直接读取配置文件来获取需要创建实例的类
		//至于如何读取Properties,还有如何反射这里就不解释了
		Properties p = new Properties(); 
		InputStream in = null;
		try {
			in = Factory.class.getResourceAsStream(
"FactoryTest.properties");
			p.load(in);
		} catch (IOException e) {
			System.out.println(
"装载工厂配置文件出错了,具体的堆栈信息如下:");
			e.printStackTrace();
		}finally{
			try {
				in.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		//用反射去创建,那些例外处理等完善的工作这里就不做了
		Api api = null;
		try {
			api = (Api)Class.forName(p.getProperty("ImplClass"))
.newInstance();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		return api;
	}
}

 (3)此时的客户端就变得很简单了,不再需要传入参数,代码示例如下:

Java代码 简单工厂方法模式
  1. public class Client {   
  2.     public static void main(String[] args) {   
  3.         Api api = Factory.createApi();   
  4.         api.test1("哈哈,不要紧张,只是个测试而已!");   
  5.     }   
  6. }  
public class Client {
	public static void main(String[] args) {
		Api api = Factory.createApi();
		api.test1("哈哈,不要紧张,只是个测试而已!");
	}
}

 把上面的示例代码敲到电脑里面,测试一下,体会体会。


3.5  简单工厂的优缺点

  • 帮助封装
        简单工厂虽然很简单,但是非常友好的帮助我们实现了组件的封装,然后让组件外部能真正面向接口编程。
  • 解耦
        通过简单工厂,实现了客户端和具体实现类的解耦。
        如同上面的例子,客户端根本就不知道具体是由谁来实现,也不知道具体是如何实现的,客户端只是通过工厂获取它需要的接口对象。
  • 可能增加客户端的复杂度
        如果通过客户端的参数来选择具体的实现类,那么就必须让客户端能理解各个参数所代表的具体功能和含义,这会增加客户端使用的难度,也部分暴露了内部实现,这种情况可以选用可配置的方式来实现。
  • 不方便扩展子工厂
        私有化简单工厂的构造方法,使用静态方法来创建接口,也就不能通过写简单工厂类的子类来改变创建接口的方法的行为了。不过,通常情况下是不需要为简单工厂创建子类的。

 


3.6  思考简单工厂

1:简单工厂的本质
        简单工厂的本质是:选择实现
        注意简单工厂的重点在选择,实现是已经做好了的。就算实现再简单,也要由具体的实现类来实现,而不是在简单工厂里面来实现。简单工厂的目的在