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

2024-01-22 00:58

本文主要是介绍研磨设计模式之 工厂方法模式-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到数据库备份文件

导出数据Test3到XML文件

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

 

3.5  工厂方法模式的优缺点 

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

3.6  思考工厂方法模式

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

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


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


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

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

3.7  相关模式

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

这篇关于研磨设计模式之 工厂方法模式-5的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/631392

相关文章

SpringBoot使用Jasypt对YML文件配置内容加密的方法(数据库密码加密)

《SpringBoot使用Jasypt对YML文件配置内容加密的方法(数据库密码加密)》本文介绍了如何在SpringBoot项目中使用Jasypt对application.yml文件中的敏感信息(如数... 目录SpringBoot使用Jasypt对YML文件配置内容进行加密(例:数据库密码加密)前言一、J

Spring Boot 中正确地在异步线程中使用 HttpServletRequest的方法

《SpringBoot中正确地在异步线程中使用HttpServletRequest的方法》文章讨论了在SpringBoot中如何在异步线程中正确使用HttpServletRequest的问题,... 目录前言一、问题的来源:为什么异步线程中无法访问 HttpServletRequest?1. 请求上下文与线

解读为什么@Autowired在属性上被警告,在setter方法上不被警告问题

《解读为什么@Autowired在属性上被警告,在setter方法上不被警告问题》在Spring开发中,@Autowired注解常用于实现依赖注入,它可以应用于类的属性、构造器或setter方法上,然... 目录1. 为什么 @Autowired 在属性上被警告?1.1 隐式依赖注入1.2 IDE 的警告:

SpringBoot快速接入OpenAI大模型的方法(JDK8)

《SpringBoot快速接入OpenAI大模型的方法(JDK8)》本文介绍了如何使用AI4J快速接入OpenAI大模型,并展示了如何实现流式与非流式的输出,以及对函数调用的使用,AI4J支持JDK8... 目录使用AI4J快速接入OpenAI大模型介绍AI4J-github快速使用创建SpringBoot

Android开发中gradle下载缓慢的问题级解决方法

《Android开发中gradle下载缓慢的问题级解决方法》本文介绍了解决Android开发中Gradle下载缓慢问题的几种方法,本文给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧... 目录一、网络环境优化二、Gradle版本与配置优化三、其他优化措施针对android开发中Gradle下载缓慢的问

python 3.8 的anaconda下载方法

《python3.8的anaconda下载方法》本文详细介绍了如何下载和安装带有Python3.8的Anaconda发行版,包括Anaconda简介、下载步骤、安装指南以及验证安装结果,此外,还介... 目录python3.8 版本的 Anaconda 下载与安装指南一、Anaconda 简介二、下载 An

Java中将异步调用转为同步的五种实现方法

《Java中将异步调用转为同步的五种实现方法》本文介绍了将异步调用转为同步阻塞模式的五种方法:wait/notify、ReentrantLock+Condition、Future、CountDownL... 目录异步与同步的核心区别方法一:使用wait/notify + synchronized代码示例关键

Python使用Pandas对比两列数据取最大值的五种方法

《Python使用Pandas对比两列数据取最大值的五种方法》本文主要介绍使用Pandas对比两列数据取最大值的五种方法,包括使用max方法、apply方法结合lambda函数、函数、clip方法、w... 目录引言一、使用max方法二、使用apply方法结合lambda函数三、使用np.maximum函数

Qt 中集成mqtt协议的使用方法

《Qt中集成mqtt协议的使用方法》文章介绍了如何在工程中引入qmqtt库,并通过声明一个单例类来暴露订阅到的主题数据,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友一起看看吧... 目录一,引入qmqtt 库二,使用一,引入qmqtt 库我是将整个头文件/源文件都添加到了工程中进行编译,这样 跨平台

Nginx设置连接超时并进行测试的方法步骤

《Nginx设置连接超时并进行测试的方法步骤》在高并发场景下,如果客户端与服务器的连接长时间未响应,会占用大量的系统资源,影响其他正常请求的处理效率,为了解决这个问题,可以通过设置Nginx的连接... 目录设置连接超时目的操作步骤测试连接超时测试方法:总结:设置连接超时目的设置客户端与服务器之间的连接