??????? 首先来解决一个常见的疑问:可能有朋友会认为,上面示例中的简单工厂看起来不就是把客户端里面的“new Impl()”移动到简单工厂里面吗?不还是一样通过new一个实现类来得到接口吗?把“new Impl()”这句话放到客户端和放到简单工厂里面有什么不同吗?
??????? 理解这个问题的重点就在于理解简单工厂所处的位置。
??????? 根据前面的学习,我们知道接口是用来封装隔离具体的实现的,目标就是不要让客户端知道封装体内部的具体实现。简单工厂的位置是位于封装体内的,也就是简单工厂是跟接口和具体的实现在一起的,算是封装体内部的一个类,所以简单工厂知道具体的实现类是没有关系的。整理一下简单工厂的结构图,新的图如图7所示:
??????????????????????????????????????? 图7? 整理后的简单工厂结构
???????
??????? 图7中虚线框,就好比是一个组件的包装边界,表示接口、实现类和工厂类组合成了一个组件,在这个封装体里面,只有接口和工厂是对外的,也就是让外部知道并使用的,所以故意漏了一些在虚线框外,而具体的实现类是不对外的,被完全包含在虚线框内。
??????? 对于客户端而言,只是知道了接口Api和简单工厂Factory,通过Factory就可以获得Api了,这样就达到了让Client在不知道具体实现类的情况下获取接口Api。
??????? 所以看似简单的把“new Impl()”这句话从客户端里面移动到了简单工厂里面,其实是有了质的变化的。
(1)简单工厂的功能
??????? 工厂嘛,就是用来造东西的。在Java里面,通常情况下是用来造接口的,但是也可以造抽象类,甚至是一个具体的类实例。
???????? 一定要注意,虽然前面的示例是利用简单工厂来创建的接口,但是也是可以用简单工厂来创建抽象类或者是普通类的实例的。
(2)静态工厂
??????? 使用简单工厂的时候,通常不用创建简单工厂类的类实例,没有创建实例的必要。因此可以把简单工厂类实现成一个工具类,直接使用静态方法就可以了,也就是说简单工厂的方法通常都是静态的,所以也被称为静态工厂。如果要防止客户端无谓的创造简单工厂实例,还可以把简单工厂的构造方法私有化了。
(3)万能工厂
??????? 一个简单工厂可以包含很多用来构造东西的方法,这些方法可以创造不同的接口、抽象类或者是类实例,一个简单工厂理论上可以构造任何东西,所以又称之为“万能工厂”。
?????? 虽然上面的实例中,在简单工厂里面只有一个方法,但事实上,是可以有很多这样创建方法的,这点要注意。
(4)简单工厂创建对象的范围
?????? 虽然从理论上讲,简单工厂什么都能造,但对于简单工厂可创建对象的范围,通常不要太大,建议控制在一个独立的组件级别或者一个模块级别,也就是一个组件或模块一个简单工厂。否则这个简单工厂类会职责不明,有点大杂烩的感觉。
(5)简单工厂的调用顺序示意图
?????? 简单工厂的调用顺序如图8所示:
?图8? 简单工厂的调用顺序示意图
(6)简单工厂命名的建议
?
?
?
??????? 虽然说简单工厂的方法多是用来造接口的,但是仔细分析就会发现,真正能实现功能的是具体的实现类,这些实现类是已经做好的,并不是真的靠简单工厂来创造出来的,简单工厂的方法无外乎就是:实现了选择一个合适的实现类来使用。
??????? 所以简单工厂方法的内部主要实现的功能是“选择合适的实现类”来创建实例对象。既然要实现选择,那么就需要选择的条件或者是选择的参数,选择条件或者是参数的来源通常又有几种:
???????? 下面来看个示例,看看由客户端来传入参数,如何写简单工厂中的方法。
(1)在刚才的示例上再添加一个实现,称为Impl2,示例代码如下:
/** * 对接口的一种实现 */ 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)要注意这种方法有一个缺点
由于是从客户端在调用工厂的时候,传入选择的参数,这就说明客户端必须知道每个参数的含义,也需要理解每个参数对应的功能处理。这就要求必须在一定程度上,向客户暴露一定的内部实现细节。
??????? 现在已经学会通过简单工厂来选择具体的实现类了,可是还有问题。比如:在现在的实现中,再新增加一种实现,会怎样呢?
??????? 那就需要修改工厂类,才能把新的实现添加到现有系统中。比如现在新加了一个实现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("哈哈,不要紧张,只是个测试而已!"); } }
?把上面的示例代码敲到电脑里面,测试一下,体会体会。
?
1:简单工厂的本质
??????? 简单工厂的本质是:选择实现。
??????? 注意简单工厂的重点在选择,实现是已经做好了的。就算实现再简单,也要由具体的实现类来实现,而不是在简单工厂里面来实现。简单工厂的目的在于为客户端来选择相应的实现,从而使得客户端和实现之间解耦,这样一来,具体实现发生了变化,就不用变动客户端了,这个变化会被简单工厂吸收和屏蔽掉。
??????? 实现简单工厂的难点就在于 “如何选择”实现,前面讲到了几种传递参数的方法,那都是静态的参数,还可以实现成为动态的参数。比如:在运行期间,由工厂去读取某个内存的值,或者是去读取数据库中的值,然后根据这个值来选择具体的实现等等。
2:何时选用简单工厂
??????? 建议在如下情况中,选用简单工厂:
?简单工厂模式结束,谢谢观赏
?