`
chjavach
  • 浏览: 461356 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

研磨设计模式之工厂方法模式-5

阅读更多

 

3.3  平行的类层次结构

(1)什么是平行的类层次结构呢?
        简单点说,假如有两个类层次结构,其中一个类层次中的每个类在另一个类层次中都有一个对应的类的结构,就被称为平行的类层次结构。
        举个例子来说,硬盘对象有很多种,如分成台式机硬盘和笔记本硬盘,在台式机硬盘的具体实现上面,又有希捷、西数等不同品牌的实现,同样在笔记本硬盘上,也有希捷、日立、IBM等不同品牌的实现;硬盘对象具有自己的行为,如硬盘能存储数据,也能从硬盘上获取数据,不同的硬盘对象对应的行为对象是不一样的,因为不同的硬盘对象,它的行为的实现方式是不一样的。如果把硬盘对象和硬盘对象的行为分开描述,那么就构成了如图10所示的结构:


 图10  平行的类层次结构示意图


        硬盘对象是一个类层次,硬盘的行为这边也是一个类层次,而且两个类层次中的类是对应的。台式机西捷硬盘对象就对应着硬盘行为里面的台式机西捷硬盘的行为;笔记本IBM硬盘就对应着笔记本IBM硬盘的行为,这就是一种典型的平行的类层次结构。
        这种平行的类层次结构用来干什么呢?主要用来把一个类层次中的某些行为分离出来,让类层次中的类把原本属于自己的职责,委托给分离出来的类去实现,从而使得类层次本身变得更简单,更容易扩展和复用。
一般来讲,分离出去的这些类的行为,会对应着类层次结构来组织,从而形成一个新的类层次结构,相当于原来对象的行为的这么一个类层次结构,而这个层次结构和原来的类层次结构是存在对应关系的,因此被称为平行的类层次结构。


(2)工厂方法模式跟平行的类层次结构有何关系呢?
        可以使用工厂方法模式来连接平行的类层次。
        看上面的示例图10,在每个硬盘对象里面,都有一个工厂方法createHDOperate,通过这个工厂方法,客户端就可以获取一个跟硬盘对象相对应的行为对象。在硬盘对象的子类里面,会覆盖父类的工厂方法createHDOperate,以提供跟自身相对应的行为对象,从而自然的把两个平行的类层次连接起来使用。


3.4  参数化工厂方法

        所谓参数化工厂方法指的就是:通过给工厂方法传递参数,让工厂方法根据参数的不同来创建不同的产品对象,这种情况就被称为参数化工厂方法。当然工厂方法创建的不同的产品必须是同一个Product类型的。
        来改造前面的示例,现在有一个工厂方法来创建ExportFileApi这个产品的对象,但是ExportFileApi接口的具体实现很多,为了方便创建的选择,直接从客户端传入一个参数,这样在需要创建ExportFileApi对象的时候,就把这个参数传递给工厂方法,让工厂方法来实例化具体的ExportFileApi实现对象。
        还是看看代码示例会比较清楚。
(1)先来看Product的接口,就是ExportFileApi接口,跟前面的示例没有任何变化,为了方便大家查看,这里重复一下,示例代码如下: 

 

/**

 * 导出的文件对象的接口

 */

public interface ExportFileApi {

    /**

     * 导出内容成为文件

     * @param data 示意:需要保存的数据

     * @return 是否导出成功

     */

    public boolean export(String data);

}

(2)同样提供保存成文本文件和保存成数据库备份文件的实现,跟前面的示例没有任何变化,示例代码如下:

 

 

public class ExportTxtFile implements ExportFileApi{

    public boolean export(String data) {

       //简单示意一下,这里需要操作文件

       System.out.println("导出数据"+data+"到文本文件");

       return true;

    }

}

public class ExportDB implements ExportFileApi{

    public boolean export(String data) {

       //简单示意一下,这里需要操作数据库和文件

       System.out.println("导出数据"+data+"到数据库备份文件");

       return true;

    }

}

(3)接下来该看看ExportOperate类了,这个类的变化大致如下:

  • ExportOperate类中的创建产品的工厂方法,通常需要提供默认的实现,不抽象了,也就是变成正常方法
  • ExportOperate类也不再定义成抽象类了,因为有了默认的实现,客户端可能需要直接使用这个对象
  • 设置一个导出类型的参数,通过export方法从客户端传入

看看代码吧,示例代码如下:

 

 

/**

 * 实现导出数据的业务功能对象

 */

 

 
public class ExportOperate {

    /**

     * 导出文件

     * @param type 用户选择的导出类型

 

 

     * @param data 需要保存的数据

     * @return 是否成功导出文件

     */

    public boolean export(int type,String data){

       //使用工厂方法

       ExportFileApi api = factoryMethod(type);

       return api.export(data);

    }

    /**

     * 工厂方法,创建导出的文件对象的接口对象

     * @param type 用户选择的导出类型

     * @return 导出的文件对象的接口对象

     */

    protected ExportFileApi factoryMethod(int type){

 

 
       ExportFileApi api = null;

       //根据类型来选择究竟要创建哪一种导出文件对象

       if(type==1){

           api = new ExportTxtFile();

       }else if(type==2){

           api = new ExportDB();

       }

       return api;

    }

}

(4)此时的客户端,非常简单,直接使用ExportOperate类,示例代码如下:

 

 

public class Client {

    public static void main(String[] args) {

        //创建需要使用的Creator对象

       ExportOperate operate = new ExportOperate();

       //调用输出数据的功能方法,传入选择到处类型的参数

       operate.export(1,"测试数据");

    }

}

       测试看看,然后修改一下客户端的参数,体会一下通过参数来选择具体的导出实现的过程。这是一种很常见的参数化工厂方法的实现方式,但是也还是有把参数化工厂方法实现成为抽象的,这点要注意,并不是说参数化工厂方法就不能实现成为抽象类了。只是一般情况下,参数化工厂方法,在父类都会提供默认的实现。


(5)扩展新的实现
        使用参数化工厂方法,扩展起来会非常容易,已有的代码都不会改变,只要新加入一个子类来提供新的工厂方法实现,然后在客户端使用这个新的子类即可。
        这种实现方式还有一个有意思的功能,就是子类可以选择性覆盖,不想覆盖的功能还可以返回去让父类来实现,很有意思。
        先扩展一个导出成xml文件的实现,试试看,示例代码如下: 

 

/**

 * 导出成xml文件的对象

 */

public class ExportXml implements ExportFileApi{

    public boolean export(String data) {

       //简单示意一下

       System.out.println("导出数据"+data+"XML文件");

        return true;

    }

}

然后扩展ExportOperate类,来加入新的实现,示例代码如下:

 

 

/**

 * 扩展ExportOperate对象,加入可以导出XML文件

 */

public class ExportOperate2 extends ExportOperate{

    /**

     * 覆盖父类的工厂方法,创建导出的文件对象的接口对象

     * @param type 用户选择的导出类型

     * @return 导出的文件对象的接口对象

     */

    protected ExportFileApi factoryMethod(int type){

       ExportFileApi api = null;

       //可以全部覆盖,也可以选择自己感兴趣的覆盖,

       //这里只想添加自己新的实现,其它的不管

       if(type==3){

           api = new ExportXml();

       }else{

           //其它的还是让父类来实现

           api = super.factoryMethod(type);

       }

       return api;

    }

}

看看此时的客户端,也非常简单,只是在变换传入的参数,示例代码如下: 

 

public class Client {

    public static void main(String[] args) {

       //创建需要使用的Creator对象

       ExportOperate operate = new ExportOperate2();

       //下面变换传入的参数来测试参数化工厂方法

       operate.export(1,"Test1");

       operate.export(2,"Test2");

       operate.export(3,"Test3");

    }

}

对应的测试结果如下: 

 

导出数据Test1到文本文件

导出数据Test2到数据库备份文件

导出数据Test3XML文件


通过上面的示例,好好体会一下参数化工厂方法的实现和带来的好处。


3.5  工厂方法模式的优缺点

  • 可以在不知具体实现的情况下编程
        工厂方法模式可以让你在实现功能的时候,如果需要某个产品对象,只需要使用产品的接口即可,而无需关心具体的实现。选择具体实现的任务延迟到子类去完成。
  • 更容易扩展对象的新版本
        工厂方法给子类提供了一个挂钩,使得扩展新的对象版本变得非常容易。比如上面示例的参数化工厂方法实现中,扩展一个新的导出Xml文件格式的实现,已有的代码都不会改变,只要新加入一个子类来提供新的工厂方法实现,然后在客户端使用这个新的子类即可。
        另外这里提到的挂钩,就是我们经常说的钩子方法(hook),这个会在后面讲模板方法模式的时候详细点说明。
  • 连接平行的类层次
        工厂方法除了创造产品对象外,在连接平行的类层次上也大显身手。这个在前面已经详细讲述了。
  • 具体产品对象和工厂方法的耦合性
        在工厂方法模式里面,工厂方法是需要创建产品对象的,也就是需要选择具体的产品对象,并创建它们的实例,因此具体产品对象和工厂方法是耦合的。

3.6  思考工厂方法模式

1:工厂方法模式的本质
        工厂方法模式的本质:延迟到子类来选择实现
        仔细体会前面的示例,你会发现,工厂方法模式中的工厂方法,在真正实现的时候,一般是先选择具体使用哪一个具体的产品实现对象,然后创建这个具体产品对象的示例,然后就可以返回去了。也就是说,工厂方法本身并不会去实现产品接口,具体的产品实现是已经写好了的,工厂方法只要去选择实现就好了。
       有些朋友可能会说,这不是跟简单工厂一样吗?
       确实从本质上讲,它们是非常类似的,具体实现上都是在“选择实现”。但是也存在不同点,简单工厂是直接在工厂类里面进行“选择实现”;而工厂方法会把这个工作延迟到子类来实现,工厂类里面使用工厂方法的地方是依赖于抽象而不是具体的实现,从而使得系统更加灵活,具有更好的可维护性和可扩展性。
       其实如果把工厂模式中的Creator退化一下,只提供工厂方法,而且这些工厂方法还都提供默认的实现,那不就变成了简单工厂了吗?比如把刚才示范参数化工厂方法的例子代码拿过来再简化一下,你就能看出来,写得跟简单工厂是差不多的,示例代码如下:

        看完上述代码,会体会到简单工厂和工厂方法模式是有很大相似性的了吧,从某个角度来讲,可以认为简单工厂就是工厂方法模式的一种特例,因此它们的本质是类似的,也就不足为奇了。


2:对设计原则的体现
        工厂方法模式很好的体现了“依赖倒置原则”。
        依赖倒置原则告诉我们“要依赖抽象,不要依赖于具体类”,简单点说就是:不能让高层组件依赖于低层组件,而且不管高层组件还是低层组件,都应该依赖于抽象。
        比如前面的示例,实现客户端请求操作的ExportOperate就是高层组件;而具体实现数据导出的对象就是低层组件,比如ExportTxtFile、ExportDB;而ExportFileApi接口就相当于是那个抽象。
        对于ExportOperate来说,它不关心具体的实现方式,它只是“面向接口编程”;对于具体的实现来说,它只关心自己“如何实现接口”所要求的功能。
        那么倒置的是什么呢?倒置的是这个接口的“所有权”。事实上,ExportFileApi接口中定义的功能,都是由高层组件ExportOperate来提出的要求,也就是说接口中的功能,是高层组件需要的功能。但是高层组件只是提出要求,并不关心如何实现,而低层组件,就是来真正实现高层组件所要求的接口功能的。因此看起来,低层实现的接口的所有权并不在底层组件手中,而是倒置到高层组件去了。

3:何时选用工厂方法模式
        建议在如下情况中,选用工厂方法模式:

  • 如果一个类需要创建某个接口的对象,但是又不知道具体的实现,这种情况可以选用工厂方法模式,把创建对象的工作延迟到子类去实现
  • 如果一个类本身就希望,由它的子类来创建所需的对象的时候,应该使用工厂方法模式


3.7  相关模式

  • 工厂方法模式和抽象工厂模式
        这两个模式可以组合使用,具体的放到抽象工厂模式中去讲。
  • 工厂方法模式和模板方法模式
        这两个模式外观类似,都是有一个抽象类,然后由子类来提供一些实现,但是工厂方法模式的子类专注的是创建产品对象,而模板方法模式的子类专注的是为固定的算法骨架提供某些步骤的实现。
        这两个模式可以组合使用,通常在模板方法模式里面,使用工厂方法来创建模板方法需要的对象。

 

工厂方法模式结束,谢谢观看!

  • 大小: 37.2 KB
  • 大小: 21.8 KB
87
6
分享到:
评论
15 楼 icekzl 2010-09-07  
关于 ioc和工厂方法的使用,一点点意见 不一定对。说出来大家讨论。
这个type参数 ,我一般会使用ioc容器中定义的 beanName/id 如spring ioc
protected ExportFileApi factoryMethod(String beanName){
       ExportFileApi api =null;
       try(){
         api = (ExportFileApi)Ioc.getBean(beanName)
       }catch(...){
         ...
       }  
       return api;
    }
}
要是又不要暴露出去 public static 再用,这里 只是关注以前 自己第一个感觉,用子类实现的地方 ,尽量不要使用if else 避免未来扩展使用的地方 if-else 泛滥。再就是 使用者 可以不必new 新子类使用,只是 传递数据依赖而已。比如:
public class Client {
    public static void main(String[] args) {
      //创建需要使用的Creator对象  %比如这里加入xml%
       ExportOperate operate = new xmlExportOperate();
         就可以不需要了。
      可以一直使用
          ExportOperate operate  new =baseOperate()

       //下面变换传入的参数来测试参数化工厂方法
        operate.export(xml,"Test1");
       operate.export(db,"Test2");
       operate.export(txt,"Test3");

    }

}


新的子类中 也不必 特意加入  if -else来处理选择了。
protected ExportFileApi factoryMethod(int type){

       ExportFileApi api = null;

       //可以全部覆盖,也可以选择自己感兴趣的覆盖,
       //这里只想添加自己新的实现,其它的不管
       if(type==3){

           api = new ExportXml();

       }else{

           //其它的还是让父类来实现
           api = super.factoryMethod(type);

       }

       return api;

    }
引入新的实现子类 只要注册到ioc中,代码侵入几乎为零老的代码基本不需要过多掉正。因为 beanName 可能是 session数据也可能是db数据  ,本着 数据依赖为最小依赖的想法,觉得  工厂方法和ioc配合 有的时候还是可以用的。
   听兄台讲解设计模式,感觉轻松愉快,非常支持。要是有出书,一定支持 希望有机会拿个签名版。。^_^(失业中的小弟)
14 楼 as_you_2007 2010-08-25  
讲解真的很透彻啊,期待博主关于抽象工厂模式的文章。
13 楼 chjavach 2010-07-16  
liuyupy 写道
该系列的讲解精彩细致,唯一不足之处就是示例不具备应用场景的代表性(或是没体现出来,当然可以通过想象创建需求),若是在最后部分其它框架相关应用 浅示 ,会让绕梁之味更浓.

论坛中讲模式的文章能 深广兼顾 新老皆懂 的,无出其二.再赞.


    确实有这个遗憾,主要是目的在于深入的讲解模式本身,没有过多的涉及具体的业务,这里提业务也是做为讲解模式的载体而已,不过我想,任何事情都很难尽善尽美,要有所得或许就必有所弃,呵呵.
    对于模式和具体的应用场景的关系,有机会再深入的写写,而且,一个模式可以在很多很多的应用场景出现,难免会出现挂一漏万的现象,也只能是当抛砖引玉了.
   
12 楼 jiangduxi 2010-07-14  
lz对工厂方法模式的1-5篇讲解,让我学会很多。非常感谢楼主!
11 楼 liuyupy 2010-07-12  
该系列的讲解精彩细致,唯一不足之处就是示例不具备应用场景的代表性(或是没体现出来,当然可以通过想象创建需求),若是在最后部分其它框架相关应用 浅示 ,会让绕梁之味更浓.

论坛中讲模式的文章能 深广兼顾 新老皆懂 的,无出其二.再赞.
10 楼 chjavach 2010-07-10  
javaisgod 写道
博主,小弟做了一年java开发,设计模式就是听说过的水平。认真拜读博主关于模式设计的文章,甚是喜欢:一是喜欢文章的结构,提出问题,分析问题,引入方法,解决问题,最后是心得体会;二是喜欢语言朴实,内容丰富。这里期待续集

另外这里有俩个疑问:1.关于文章中模式的一些概念的说明是大牛自己的理解,还是权威解释。
2."工厂方法模式的本质:延迟到子类来选择实现。"不是太明白。是写代码的时候延时到子类实现;还程序运行时延时到子类实现。




    谢谢夸奖,我的目的也是写出点东西来,不是搞很多花哨的故事,看起来轻松,看完就扔,因为没货;而是根据实践经验,除了经典的理论,再结合实际应用来阐述,帮助更多的朋友真正理解设计模式,并应用设计模式。
      回答你的疑问:
“1.关于文章中模式的一些概念的说明是大牛自己的理解,还是权威解释。

答:概念说明大都来自GoF的设计模式一书,绝对正统,当然也还有一些个人的理解和经验总结。在回答另外一个朋友的问题的时候也说过,我没有看过其他的设计模式的书,十年来只看GoF的设计模式,GoF的书是所有讲设计模式的根源,大家都是从它那里来的。

“2."工厂方法模式的本质:延迟到子类来选择实现。"不是太明白。是写代码的时候延时到子类实现;还程序运行时延时到子类实现。”

答:两者都对,一个是开发期,一个是运行期。
   
9 楼 javaisgod 2010-07-09  
博主,小弟做了一年java开发,设计模式就是听说过的水平。认真拜读博主关于模式设计的文章,甚是喜欢:一是喜欢文章的结构,提出问题,分析问题,引入方法,解决问题,最后是心得体会;二是喜欢语言朴实,内容丰富。这里期待续集

另外这里有俩个疑问:1.关于文章中模式的一些概念的说明是大牛自己的理解,还是权威解释。
2."工厂方法模式的本质:延迟到子类来选择实现。"不是太明白。是写代码的时候延时到子类实现;还程序运行时延时到子类实现。

8 楼 ssooss 2010-07-06  
待续,一直开下去
7 楼 clctcx 2010-06-30  
看完工厂方法模式了,写得太棒了,鼓掌
6 楼 lqingle 2010-06-27  
5 楼 printout 2010-06-25  
一路看下来,把工厂方法模式1-5都看完了,写得确实不错,继续追看后面的
4 楼 fangfusdui 2010-06-21  
写得不错,楼主加油
3 楼 dakaiopen 2010-06-21  
连着看完了1-5,好就一个字,期待楼主更多好的文章
2 楼 vinkeychen 2010-06-20  
受益匪浅啊。
1 楼 weijiezhimi 2010-06-20  
对工厂方法模式的本质总结得不错,引人思考,对设计原则的体现也还可以,要是再深入点会更好,比如结合上"里氏替换原则"

相关推荐

Global site tag (gtag.js) - Google Analytics