首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 软件管理 > 软件开发 >

设计方式-简单工厂

2012-11-04 
设计模式-简单工厂第2章?简单工厂??简单工厂不是一个标准的设计模式,但是它实在是太常用了,简单而又神奇,

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


2.1?场 景 问 题大家都知道,在Java应用开发中,要“面向接口编程”。那么什么是接口?接口有什么作用?接口如何使用?我们一起来回顾一下。2.1.1?接口回顾1:Java中接口的概念在Java中接口是一种特殊的抽象类,跟一般的抽象类相比,接口里面的所有方法都是抽象方法,接口里面的所有属性都是常量。也就是说,接口里面只有方法定义而没有任何方法实现。2:接口用来干,外部调用只能通过接口进行调用,外部调用是不知道内部具体实现的,也就是说外部调用和内部实现是被接口隔离开的。4:使用接口的好处由于外部调用和内部实现被接口隔离开了,那么只要接口不变,内部实现的变化就不会影响到外部应用,从而使得系统更灵活,具有更好的扩展性和可维护性,这也就是所谓“接口是系统可插拔性的保证”这句话的意思。5:接口和抽象类的选择既然接口是一种特殊的抽象类,那么在开发中,何时选用接口?何时选用抽象类呢?对于它们的选择,在开发中是一个很重要的问题,特别总结两句话给大家:n?????????? 优先选用接口n?????????? 在既要定义子类的行为,又要为子类提供公共的功能时应选择抽象类。2.1.2?面向接口编程面向接口编程是Java编程中的一个重要原则。在Java 程序设计里面,非常讲究层的划分和模块的划分。通常按照三层来划分Java程序,分别是表现层、逻辑层和数据层,它们之间都要通过接口来通信。在每一个层里面,又有很多个小模块,每个小模块对外则是一个整体,所以一个模块对外应该提供接口,其他地方需要使用到这个模块的功能时,可以通过此接口来进行调用。这也就是常说的“接口是被其隔离部分的外观”。基本的三层结构如图2.1所示。图2.1?基本的三层结构示意图在一个层内部的各个模块间的交互要通过接口,如图2.2所示。?图2.2?一个层内部的各个模块间的交互示意图各个部分的接口具体应该如何去定义,具体的内容是什么,我们不去深究,那是需要具体问题具体分析的,这里仅学习设计的方法。上面频频提到“组件”,那么什么是组件呢?所谓组件:从设计上讲,组件就是能完成一定功能的封装体。小到一个类,大到一个系统,都可以称为组件,因为一个小系统放到更大的系统里面去,也就当个组件而已。事实上,从设计的角度看,系统、子系统、模块、组件等说的其实是同一回事情,都是完成一定功能的封装体,只不过功能多少不同而已。继续刚才的思路,大家会发现,不管是一层还是一个模块或者一个组件,都是一个被接口隔离的整体,那么下面我们就不去区分它们,统一认为它们都是接口隔离体即可,如图2.3所示。?图2.3?接口隔离体示意图???? 既然在Java中需要面向接口编程,那么在程序中到底如何使用接口,来做到真正的面向接口编程呢?2.1.3?不用模式的解决方案回忆一下,以前是如何使用接口的呢,假设有一个接口叫Api,然后有一个实现类Impl实现了它,在客户端怎么用这个接口呢?通常都是在客户端创建一个Impl的实例,把它赋值给一个Api接口类型的变量,然后客户端就可以通过这个变量来操作接口的功能了,此时具体的结构图如图2.4所示。
图2.4?基本的接口和实现用代码来说明会更清楚一些。(1)首先定义接口Api,示例代码如下:

/*** 某个接口(通用的、抽象的、非具体的功能) */public interface Api {?/**??* 某个具体的功能方法的定义,用test1来演示一下。??* 这里的功能很简单,把传入的s打印输出即可??* @param s 任意想要打印输出的字符串??*/?public void test1(String s);}(2)既然有了接口,自然就要有实现,定义实现Impl,示例代码如下:

/*** 对接口的实现*/public class Impl implements Api{?public void test1(String s) {????? System.out.println("Now In Impl. The input s=="+s);?}}(3)那么此时的客户端怎么写呢?按照Java的知识,接口不能直接使用,需要使用接口的实现类,示例代码如下:

/*** 客户端:测试使用Api接口*/public class Client {?public static void main(String[] args) {????? Api api = new Impl();????? api.test1("哈哈,不要紧张,只是个测试而已!");?}}2.1.4?有何问题上面写得没错吧,在Java的基础知识里面就是这么学的,难道这有什么问题吗?请仔细看位于客户端的下面这句话:

提示

?????? Api api = new Impl();

?

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

?你会发现在客户端调用的时候,客户端不但知道了接口,同时还知道了具体的实现就是Impl。接口的思想是“封装隔离”,而实现类Impl应该是被接口Api封装并同客户端隔离开的,也就是说,客户端根本就不应该知道具体的实现类是Impl。

?

有朋友说,那好,我就把Impl从客户端拿掉,让Api真正的对实现进行“封装隔离”,然后我们继续面向接口来编程。可是,新的问题出现了,当他把“new Impl()”去掉后,却发现无法得到Api接口对象了,怎么办呢?把这个问题描述一下:在Java编程中,出现只知接口而不知实现,该怎么办?就像现在的Client,它知道要使用Api接口,但是不知由谁实现,也不知道如何实现,从而得不到接口对象,就无法使用接口,该怎么办呢?2.2?解 决 方 案2.2.1?使用简单工厂来解决问题???? 用来解决上述问题的一个合理的解决方案就是简单工厂,那么什么是简单工厂呢?

提供一个创建对象实例的功能,而无须关心其具体实现。被创建实例的类型可以是接口、抽象类,也可以是具体的类。

?1、简单工厂的定义

?

?2、应用简单工厂来解决问题的思路分析上面的问题,虽然不能让模块外部知道模块内部的具体实现,但是模块内部是可以知道实现类的,而且创建接口是需要具体实现类的。那么,干脆在模块内部新建一个类,在这个类里面来创建接口,然后把创建好的接口返回给客户端,这样,外部应用就只需要根据这个类来获取相应的接口对象,然后就可以操作接口定义的方法了。把这样的对象称为简单工厂,就叫它Factory吧。这样一来,客户端就可以通过Factory来获取需要的接口对象,然后调用接口的方法来实现需要的功能,而且客户端也不用再关心具体的实现了。2.2.2?简单工厂的结构和说明简单工厂的结构如图2.5所示。图2.5?简单工厂的结构示意图l?? Api定义客户所需要的功能接口。l?? Impl具体实现Api的实现类,可能会有多个。l?? Factory工厂,选择合适的实现类来创建Api接口对象。l?? Client客户端,通过Factory来获取Api接口对象,然后面向Api接口编程。2.2.3?简单工厂示例代码(1)Api定义的示例代码如下:/**?* 接口的定义,该接口可以通过简单工厂来创建?*/public interface Api {?/**??* 示意,具体功能方法的定义??* @param s 示意,需要的参数??*/?public void operation(String s);}(2)定义了接口,接下来实现它。ImplA的示例代码如下:/**?* 接口的具体实现对象A ?*/public class ImplA implements Api{?public void operation(String s) {????? //实现功能的代码,示意一下????? System.out.println("ImplA s=="+s);?}}ImplB的示意实现和ImplA基本一样。示例代码如下:/**?* 接口的具体实现对象B ?*/public class ImplB implements Api{?public void operation(String s) {????? //实现功能的代码,示意一下????? System.out.println("ImplB s=="+s);?}}(3)下面来看看简单工厂的实现。示例代码如下:/**?* 工厂类,用来创建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)再来看看客户端的示意,示例代码如下:/**?* 客户端,使用Api接口?*/public class Client {?public static void main(String[] args) {????? //通过简单工厂来获取接口对象????? Api api = Factory.createApi(1);????? api.operation("正在使用简单工厂");?}}2.2.4?使用简单工厂重写示例???? 要使用简单工厂来重写前面的示例,主要就是要创建一个简单工厂对象,让简单工厂来负责创建接口对象。然后让客户端通过工厂来获取接口对象,而不再由客户端自己去创建接口的对象了。此时系统的结构如图2.6所示。图2.6?使用简单工厂重写示例的结构示意图(1)接口Api和实现类Impl都和前面的示例一样,这里不再赘述。(2)新创建一个简单工厂的对象。示例代码如下:/**?* 工厂类,用来创建Api对象?*/public class Factory {?/**??* 具体的创建Api对象的方法??* @return 创建好的Api对象??*/?public static Api createApi(){????? //由于只有一个实现,就不用条件来判断了????? return new Impl();?}}(3)使用简单工厂。客户端如何使用简单工厂提供的功能呢?这个时候,客户端就不用再自己去创建接口的对象了,应该使用工厂来获取。经过改造,客户端代码如下:/*** 客户端:测试使用Api接口*/public class Client {?public static void main(String[] args) {???? //重要改变,没有new Impl()了,取而代之Factory.createApi()?????? Api api = Factory.createApi();????? api.test1("哈哈,不要紧张,只是个测试而已!");?}}就如同上面的示例,客户端通过简单工厂创建了一个实现接口的对象,然后面向接口编程,从客户端来看,它根本不知道具体的实现是什么,也不知道是如何实现的,它只知道通过工厂获得了一个接口对象,然后通过这个接口来获取想要的功能。事实上,简单工厂能帮助我们真正地开始面向接口编程,像以前的做法,其实只是用到了接口的多态部分的功能,而最重要的“封装隔离性”并没有体现出来。2.3?模 式 讲 解2.3.1?典型疑问

理解这个问题的重点就在于理解简单工厂所处的位置。

?提示首先来解决一个常见的问题:可能有朋友会认为,上面示例中的简单工厂看起来不就是把客户端里面的“new Impl()”移动到简单工厂里面吗?不还是一样通过new一个实现类来得到接口吗?把“new Impl()”这句话放到客户端和放到简单工厂里面有什么不同吗?

?

根据前面的学习,我们知道接口是用来封装隔离具体的实现的,目标就是不要让客户端知道封装体内部的具体实现。简单工厂的位置是位于封装体内的,也就是简单工厂是跟接口和具体的实现在一起的,算是封装体内部的一个类,所以简单工厂知道具体的实现类是没有关系的。从新整理一下简单工厂的结构图,如图2.7所示。?图2.7?整理后的简单工厂结构???? 图2.7中的虚线框,就好比是一个组件的包装边界,表示接口、实现类和工厂类组合成了一个组件。在这个封装体里面,只有接口和工厂是对外的,也就是让外部知道并使用的,所以故意漏了一些在虚线框外,而具体的实现类是不对外的,被完全包含在虚线框内。对于客户端而言,只是知道了接口Api和简单工厂Factory,通过Factory就可以获得Api了,这样就达到了让Client在不知道具体实现类的情况下获取接口Api。所以看似简单地将new Impl()这句话从客户端里面移动到了简单工厂里面,其实是有了质的变化的。2.3.2?认识简单工厂1、简单工厂的功能

一定要注意,虽然前面的示例是利用简单工厂来创建的接口,但是也可以用简单工厂来创建抽象类或普通类的实例。

?注意工厂嘛,就是用来造东西的。在Java里面,通常情况下是用来创造接口的,但是也可以创造抽象类,甚至是一个具体的类实例。

?

2、静态工厂使用简单工厂的时候,通常不用创建简单工厂类的类实例,没有创建实例的必要。?? 因此可以把简单工厂类实现成一个工具类,直接使用静态方法就可以了。也就是说简单工厂的方法通常是静态的,所以也被称为静态工厂。如果要防止客户端无谓地创造简单工厂实例,还可以把简单工厂的构造方法私有化了。3、万能工厂一个简单工厂可以包含很多用来构造东西的方法,这些方法可以创建不同的接口、抽象类或者是类实例。一个简单工厂理论上可以构造任何东西,所以又称之为“万能工厂”。虽然上面的实例在简单工厂里面只有一个方法,但事实上,是可以有很多这样的创建方法的,这点要注意。4、简单工厂创建对象的范围虽然从理论上讲,简单工厂什么都能创建,但对于简单工厂可创建对象的范围,通常不要太大,建议控制在一个独立的组件级别或者一个模块级别,也就是一个组件或模块一个简单工厂。否则这个简单工厂类会职责不明,有点大杂烩的感觉。5、简单工厂的调用顺序示意图简单工厂的调用顺序如图2.8所示:图2.8?简单工厂的调用顺序示意图6、简单工厂命名的建议n?????????? 类名称建议为“模块名称+Factory”。比如,用户模块的工厂就称为UserFactory。n?????????? 方法名称通常为“get+接口名称”或者是“create+接口名称”。比如,有一个接口名称为UserEbi,那么方法名称通常为getUserEbi 或者是 createUserEbi。当然,也有一些朋友习惯于把方法名称命名为“new+接口名称”。比如,newUserEbi,我们不提倡这样做。因为new在Java中代表特定的含义,而且通过简单工厂的方法来获取对象实例,并不一定每次都是要new一个新的实例。如果使用newUserEbi,会给人错觉,好像每次都是new一个新的实例一样。2.3.3?简单工厂中方法的写法虽然说简单工厂的方法大多是用来创建接口的,但是仔细分析就会发现,真正能实现功能的是具体的实现类,这些实现类是已经做好的,并不是真的要靠简单工厂来创造出来的,简单工厂的方法无外乎就是:实现了选择一个合适的实现类来使用。所以说简单工厂方法的内部主要实现的功能是“选择合适的实现类”来创建实例对象。既然要实现选择,那么就需要选择的条件或者是选择的参数,选择条件或者是参数的来源通常又有以下几种。n?????????? 来源于客户端,由Client来传入参数n?????????? 来源于配置文件,从配置文件获取用于判断的值n?????????? 来源于程序运行期的某个值,比如从缓存中获取某个运行期的值下面来看示例,看看由客户端来传入参数,如何写简单工厂中的方法。(1)在2.3.3节的示例上再添加一个实现,称为Impl2,示例代码如下:/**?* 对接口的一种实现?*/public class Impl2 implements Api{?public void test1(String s) {????? System.out.println("Now In Impl2. 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)要注意这种方法有一个缺点。由于是从客户端在调用工厂的时候传入选择的参数,这就说明客户端必须知道每个参数的含义,也需要理解每个参数对应的功能处理。这就要求必须在一定程度上,向客户暴露一定的内部实现细节。2.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;?}}每次新增加一个实现类都来修改工厂类的实现,肯定不是一个好的实现方式。那么现在希望新增加了实现类过后不修改工厂类