研磨设计模式之简单工厂模式-3

3  模式讲解

3.1  典型疑问

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


                                        图7  整理后的简单工厂结构
       html

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

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,示例代码以下:api

/**
 * 对接口的一种实现 
 */
public class Impl2 implements Api{
	public void test1(String s) {
		System.out.println("Now In Impl The input s=="+s);
	}
}

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

/**
 * 工厂类,用来创造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方法的时候须要传入参数,示例代码以下:工具

public class Client {
	public static void main(String[] args) {
		//注意这里传递的参数,修改参数就能够修改行为,试试看吧
		Api api = Factory.createApi(2);
		api.test1("哈哈,没关系张,只是个测试而已!");
	}
}

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

3.4  可配置的简单工厂

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

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同一个包下面,内容以下:

ImplClass=cn.javass.dp.simplefactory.example5.Impl

若是新添加了实现类,修改这里的配置就能够了,就不须要修改程序了。
(2)此时的工厂类实现以下:

/**
* 工厂类,用来创造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)此时的客户端就变得很简单了,再也不须要传入参数,代码示例以下:

public class Client {
	public static void main(String[] args) {
		Api api = Factory.createApi();
		api.test1("哈哈,没关系张,只是个测试而已!");
	}
}

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

3.5  简单工厂的优缺点

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

 

3.6  思考简单工厂

1:简单工厂的本质
        简单工厂的本质是:选择实现
        注意简单工厂的重点在选择,实现是已经作好了的。就算实现再简单,也要由具体的实现类来实现,而不是在简单工厂里面来实现。简单工厂的目的在于为客户端来选择相应的实现,从而使得客户端和实现之间解耦,这样一来,具体实现发生了变化,就不用变更客户端了,这个变化会被简单工厂吸取和屏蔽掉。
        实现简单工厂的难点就在于 “如何选择”实现,前面讲到了几种传递参数的方法,那都是静态的参数,还能够实现成为动态的参数。好比:在运行期间,由工厂去读取某个内存的值,或者是去读取数据库中的值,而后根据这个值来选择具体的实现等等。
2:什么时候选用简单工厂
        建议在以下状况中,选用简单工厂:

  • 若是想要彻底封装隔离具体实现,让外部只能经过接口来操做封装体,那么能够选用简单工厂,让客户端经过工厂来获取相应的接口,而无需关心具体实现
  • 若是想要把对外建立对象的职责集中管理和控制,能够选用简单工厂,一个简单工厂能够建立不少的、不相关的对象,能够把对外建立对象的职责集中到一个简单工厂来,从而实现集中管理和控制

3.7  相关模式

  • 简单工厂和抽象工厂模式
        简单工厂是用来选择实现的,能够选择任意接口的实现,一个简单工厂能够有多个用于选择并建立对象的方法,多个方法建立的对象能够有关系也能够没有关系。
        抽象工厂模式是用来选择产品簇的实现的,也就是说通常抽象工厂里面有多个用于选择并建立对象的方法,可是这些方法所建立的对象之间一般是有关系的,这些被建立的对象一般是构成一个产品簇所须要的部件对象。
        因此从某种意义上来讲,简单工厂和抽象工厂是相似的,若是抽象工厂退化成为只有一个实现,不分层次,那么就至关于简单工厂了。
  • 简单工厂和工厂方法模式
        简单工厂和工厂方法模式也是很是相似的。
        工厂方法的本质也是用来选择实现的,跟简单工厂的区别在于工厂方法是把选择具体实现的功能延迟到子类去实现。
        若是把工厂方法中选择的实现放到父类直接实现,那就等同于简单工厂。
  • 简单工厂和能建立对象实例的模式
        简单工厂的本质是选择实现,因此它能够跟其它任何可以具体的建立对象实例的模式配合使用,好比:单例模式、原型模式、生成器模式等等。

简单工厂模式结束,谢谢观赏


---------------------------------------------------------------------------

私塾在线学习网原创内容  跟着cc学设计系列 之 研磨设计模式

原创内容,转载请注明出处【http://sishuok.com/forum/blogPost/list/117.html

---------------------------------------------------------------------------

相关文章
相关标签/搜索