研磨设计模式之工厂方法模式(Factory Method)-模式讲解3

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  相关模式

  • 工厂方法模式和抽象工厂模式
       这两个模式可以组合使用,具体的放到抽象工厂模式中去讲。

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


本文链接:研磨设计模式之工厂方法模式(Factory Method)-模式讲解3,转自:http://chjavach.iteye.com/blog/694864

2019-03-08 16:35

知识点

相关教程

更多

研磨设计模式之工厂方法模式(Factory Method)-模式讲解1

3.1  认识工厂方法模式(1)模式的功能 工厂方法的主要功能是让父类在不知道具体实现的情况下,完成自身的功能调用,而具体的实现延迟到子类来实现...

研磨设计模式之工厂方法模式(Factory Method)-模式讲解2

3.2  工厂方法模式与IoC/DI,IoC——Inversion of Control  控制反转,DI——Dependency Injection   依赖注入,1:如何理解IoC/DI 要想理解上面两个概念,就必须搞清楚如下的问题...

研磨设计模式之工厂方法模式(Factory Method)-场景问题

1.1  导出数据的应用框架,考虑这样一个实际应用:实现一个导出数据的应用框架,来让客户选择数据的导出方式,并真正执行数据导出。在一些实际的企业应用中,一个公司的系统往往分散在很多个不同的地方运行...

研磨设计模式之工厂方法模式(Factory Method)-解决方案

2.1  工厂方法模式来解决 用来解决上述问题的一个合理的解决方案就是工厂方法模式。那么什么是工厂方法模式呢?(1)工厂方法模式定义 定义一个用于创建对象的接口,让子类决定实例化哪一个类,Factory Method使一个类的实例化延迟到其子类...

JAVA设计模式学习5——工厂方法模式

工厂方法模式(Factory Method)又称虚拟构造子模式,可以说是简单工厂的抽象,也可以理解为简单工厂是退化了的工厂方法模式,其表现在简单工厂丧失了工厂方法的多态性。我们前一节中提到当产品结构变的复杂的时候,简单工厂就变的难以应付,如果增加一种产品,核心工厂类必须改动,使得整个工厂的可扩展性变得很差,对开闭原则支持不够。工厂方法模式克服了这些缺点,它定义一个创建产品对象的工厂接口,将实际创建

研磨设计模式之简单工厂模式(模式讲解)

根据前面的学习,我们知道接口是用来封装隔离具体的实现的,目标就是不要让客户端知道封装体内部的具体实现。简单工厂的位置是位于封装体内的,也就是简单工厂是跟接口和具体的实现在一起的,算是封装体内部的一个类,所以简单工厂知道具体的实现类是没有关系的。

研磨设计模式之抽象工厂模式(Abstract Factory)-场景问题

举个生活中常见的例子——组装电脑,我们在组装电脑的时候,通常需要选择一系列的配件,比如:CPU、硬盘、内存、主板、电源、机箱等等。为了使讨论简单点,只考虑选择CPU和主板的问题...

研磨设计模式之简单工厂模式(场景问题)

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

研磨设计模式之适配器模式(Adapter)-模式讲解

4.3  模式讲解 4.3.1  认识适配器模式(1)模式的功能 适配器模式的主要功能是进行转换匹配,目的是复用已有的功能,而不是来实现新的接口。也就是说,客户端需要的功能应该是已经实现好了的,不需要适配器模式来实现,适配器模式主要负责把不兼容的接口转换成客户端期望的样子就好了...

研磨设计模式之外观模式(Facade)(模式讲解)

(1)外观模式的目的 外观模式的目的不是给子系统添加新的功能接口,而是为了让外部减少与子系统内多个模块的交互,松散耦合,从而让外部能够更简单的使用子系统。

研磨设计模式之单例模式(Singleton)-模式讲解1

3.1  认识单例模式(1)单例模式的功能 单例模式的功能是用来保证这个类在运行期间只会被创建一个类实例,另外单例模式还提供了一个全局唯一访问这个类实例的访问点,就是那个getInstance的方法。不管采用懒汉式还是饿汉式的实现方式,这个全局访问点是一样的...

研磨设计模式之单例模式(Singleton)-模式讲解2

3.3  延迟加载的思想 单例模式的懒汉式实现方式体现了延迟加载的思想,什么是延迟加载呢?通俗点说,就是一开始不要加载资源或者数据,一直等,等到马上就要使用这个资源或者数据了,躲不过去了才加载,所以也称Lazy Load,不是懒惰啊,是“延迟加载”...

研磨设计模式之单例模式(Singleton)-模式讲解3

3.8  在Java中一种更好的单例实现方式,根据上面的分析,常见的两种单例实现方式都存在小小的缺陷,那么有没有一种方案,既能够实现延迟加载,又能够实现线程安全呢?还真有高人想到这样的解决方案了...

设计模式-工厂模式


                            

JAVA设计模式学习4——简单工厂模式

从这节开始学习设计模式,首先学习创建模式,其中工厂模式是创建模式里面最常见也常用的一种,工厂模式又分简单工厂模式(Simple Factory),工厂方法模式(Factory Method)和抽象工厂模式(Abstractor Factory),这里先学习最简单的也就是简单工厂模式。 简单工厂模式(Simple Factory)也称静态工厂方法模式,是工厂方法模式的特殊实现。简单工厂模式的一般性结

最新教程

更多

java线程状态详解(6种)

java线程类为:java.lang.Thread,其实现java.lang.Runnable接口。 线程在运行过程中有6种状态,分别如下: NEW:初始状态,线程被构建,但是还没有调用start()方法 RUNNABLE:运行状态,Java线程将操作系统中的就绪和运行两种状态统称为“运行状态” BLOCK:阻塞状态,表示线程阻塞

redis从库只读设置-redis集群管理

默认情况下redis数据库充当slave角色时是只读的不能进行写操作,如果写入,会提示以下错误:READONLY You can't write against a read only slave.  127.0.0.1:6382> set k3 111  (error) READONLY You can't write against a read only slave. 如果你要开启从库

Netty环境配置

netty是一个java事件驱动的网络通信框架,也就是一个jar包,只要在项目里引用即可。

Netty基于流的传输处理

​在TCP/IP的基于流的传输中,接收的数据被存储到套接字接收缓冲器中。不幸的是,基于流的传输的缓冲器不是分组的队列,而是字节的队列。 这意味着,即使将两个消息作为两个独立的数据包发送,操作系统也不会将它们视为两个消息,而只是一组字节(有点悲剧)。 因此,不能保证读的是您在远程定入的行数据

Netty入门实例-使用POJO代替ByteBuf

使用TIME协议的客户端和服务器示例,让它们使用POJO来代替原来的ByteBuf。

Netty入门实例-时间服务器

Netty中服务器和客户端之间最大的和唯一的区别是使用了不同的Bootstrap和Channel实现

Netty入门实例-编写服务器端程序

channelRead()处理程序方法实现如下

Netty开发环境配置

最新版本的Netty 4.x和JDK 1.6及更高版本

电商平台数据库设计

电商平台数据库表设计:商品分类表、商品信息表、品牌表、商品属性表、商品属性扩展表、规格表、规格扩展表

HttpClient 上传文件

我们使用MultipartEntityBuilder创建一个HttpEntity。 当创建构建器时,添加一个二进制体 - 包含将要上传的文件以及一个文本正文。 接下来,使用RequestBuilder创建一个HTTP请求,并分配先前创建的HttpEntity。

MongoDB常用命令

查看当前使用的数据库    > db    test  切换数据库   > use foobar    switched to db foobar  插入文档    > post={"title":"领悟书生","content":"这是一个分享教程的网站","date":new

快速了解MongoDB【基本概念与体系结构】

什么是MongoDB MongoDB is a general purpose, document-based, distributed database built for modern application developers and for the cloud era. MongoDB是一个基于分布式文件存储的数据库。由C++语言编写。旨在为WEB应用提供可扩展的高性能数据存储解决方案。

windows系统安装MongoDB

安装 下载MongoDB的安装包:mongodb-win32-x86_64-2008plus-ssl-3.2.10-signed.msi,按照提示步骤安装即可。 安装完成后,软件会安装在C:\Program Files\MongoDB 目录中 我们要启动的服务程序就是C:\Program Files\MongoDB\Server\3.2\bin目录下的mongod.exe,为了方便我们每次启动,我

Spring boot整合MyBatis-Plus 之二:增删改查

基于上一篇springboot整合MyBatis-Plus之后,实现简单的增删改查 创建实体类 添加表注解TableName和主键注解TableId import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.annotations.TableName;
import com.baom

分布式ID生成器【snowflake雪花算法】

基于snowflake雪花算法分布式ID生成器 snowflake雪花算法分布式ID生成器几大特点: 41bit的时间戳可以支持该算法使用到2082年 10bit的工作机器id可以支持1024台机器 序列号支持1毫秒产生4096个自增序列id 整体上按照时间自增排序 整个分布式系统内不会产生ID碰撞 每秒能够产生26万ID左右 Twitter的 Snowflake分布式ID生成器的JAVA实现方案