【白话设计模式五】工厂方法模式(Factory Method)

#0 系列目录#算法

#1 场景问题# ##1.1 导出数据的应用框架## 考虑这样一个实际应用:实现一个导出数据的应用框架,来让客户选择数据的导出方式,并真正执行数据导出。数据库

在一些实际的企业应用中,一个公司的系统每每分散在不少个不一样的地方运行,好比各个分公司或者是门市点,公司没有创建全公司专网的实力,可是又不肯意让业务数据实时的在广域网上传递,一个是考虑数据安全的问题,一个是运行速度的问题。编程

这种系统一般会有一个折中的方案,那就是各个分公司内运行系统的时候是独立的,是在本身分公司的局域网内运行。而后在天天业务结束的时候,各个分公司会导出本身的业务数据,而后把业务数据打包经过网络传送给总公司,或是专人把数据送到总公司,而后由总公司进行数据导入和核算。设计模式

一般这种系统,在导出数据上,会有一些约定的方式,好比导出成:文本格式、数据库备份形式、Excel格式、Xml格式等等。api

如今就来考虑实现这样一个应用框架。在继续以前,先来了解一些关于框架的知识。安全

##1.2 框架的基础知识##网络

  1. 框架是什么

简单点说:框架就是能完成必定功能的半成品软件。架构

就其本质而言,框架是一个软件,并且是一个半成品的软件。所谓半成品,就是还不能彻底实现用户须要的功能,框架只是实现用户须要的功能的一部分,还须要进一步加工,才能成为一个知足用户须要的、完整的软件。所以框架级的软件,它的主要客户是开发人员,而不是最终用户。框架

有些朋友会想,既然框架只是个半成品,那何须要去学习和使用框架呢?学习成本也不算小,那就是由于框架能完成必定的功能,也就是这“框架已经完成的必定的功能”在吸引着开发人员,让你们投入去学习和使用框架。学习

  1. 框架能干什么

能完成必定功能,加快应用开发进度。

因为框架完成了必定的功能,并且一般是一些基础的、有难度的、通用的功能,这就避免咱们在应用开发的时候彻底从头开始,而是在框架已有的功能之上继续开发,也就是说会复用框架的功能,从而加快应用的开发进度。

给咱们一个精良的程序架构。框架定义了应用的总体结构,包括类和对象的分割,各部分的主要责任,类和对象怎么协做,以及控制流程等等。

如今Java界大多数流行的框架,大都出自大师手笔,设计都很精良。基于这样的框架来开发,通常会遵循框架已经规划好的结构来进行开发,从而让咱们开发的应用程序的结构也相对变得精良了。

  1. 对框架的理解

基于框架来开发,事情仍是那些事情,只是看谁作的问题。

对于应用程序和框架的关系,能够用一个图来简单描述一下,如图所示:

输入图片说明

若是没有框架,那么客户要求的全部功能都由开发人员本身来开发,没问题,一样能够实现用户要求的功能,只是开发人员的工做多点。

若是有了框架,框架自己完成了必定的功能,那么框架已有的功能,开发人员就能够不作了,开发人员只须要完成框架没有的功能,最后一样是完成客户要求的全部功能,可是开发人员的工做就减小了。

也就是说,基于框架来开发,软件要完成的功能并无变化,仍是客户要求的全部功能,也就是“事情仍是那些事情”的意思。可是有了框架事后,框架完成了一部分功能,而后开发人员再完成一部分功能,最后由框架和开发人员合起来完成了整个软件的功能,也就是看这些功能“由谁作”的问题。

基于框架开发,能够不去作框架所作的事情,可是应该明白框架在干什么,以及框架是如何实现相应功能的

事实上,在实际开发中,应用程序和框架的关系,一般都不会如上面讲述的那样,分得那么清楚,更为广泛的是相互交互的,也就是应用程序作一部分工做,而后框架作一部分工做,而后应用程序再作一部分工做,而后框架再作一部分工做,如此交错,最后由应用程序和框架组合起来完成用户的功能要求。

也用个图来讲明,如图所示:

输入图片说明

若是把这个由应用程序和框架组合在一块儿构成的矩形,看成最后完成的软件。试想一下,若是你不懂框架在干什么的话,至关于框架对你来说是个黑盒,也就是至关于在上面图中,去掉框架的两块,会发现什么?没错,剩下的应用程序是支离破碎的,是相互分隔开来的。

这会致使一个很是致命的问题,整个应用是如何运转起来的,你是不清楚的,也就是说对你而言,项目已经失控了,从项目管理的角度来说,这是很危险的。

所以,在基于框架开发的时候,虽然咱们能够不去作框架所作的事情,可是应该搞明白框架在干什么,若是条件许可的话,还应该搞清楚框架是如何实现相应功能的,至少应该把大体的实现思路和实现步骤搞清楚,这样咱们才能总体的掌控整个项目,才能尽可能减小出现项目失控的状况。

  1. 框架和设计模式的关系

设计模式比框架更抽象。

框架已是实现出来的软件了,虽然只是个半成品的软件,但毕竟是已经实现出来的了。而设计模式的重心还在于解决问题的方案上,也就是还停留在思想的层面。所以设计模式比框架更为抽象

设计模式是比框架更小的体系结构元素。

如上所述,框架是已经实现出来的软件,并实现了一系列的功能,所以一个框架,一般会包含多个设计模式的应用。

框架比设计模式更加特例化。

框架是完成必定功能的半成品软件,也就是说,框架的目的很明确,就是要解决某一个领域的某些问题,那是很具体的功能,不一样的领域实现出来的框架是不同的。

而设计模式还停留在思想的层面,在不一样的领域均可以应用,只要相应的问题适合用某个设计模式来解决。所以框架老是针对特定领域的,而设计模式更加注重从思想上,从方法上来解决问题,更加通用化。

##1.3 有何问题## 分析上面要实现的应用框架,无论用户选择什么样的导出格式,最后导出的都是一个文件,并且系统并不知道究竟要导出成为何样的文件,所以应该有一个统一的接口,来描述系统最后生成的对象,并操做输出的文件

先把导出的文件对象的接口定义出来,示例代码以下:

/**
 * 导出的文件对象的接口
 */
public interface ExportFileApi {
   /**
    * 导出内容成为文件
    * @param data 示意:须要保存的数据
    * @return 是否导出成功
    */
   public boolean export(String data);
}

对于实现导出数据的业务功能对象,它应该根据须要来建立相应的ExportFileApi的实现对象,由于特定的ExportFileApi的实现是与具体的业务相关的。可是对于实现导出数据的业务功能对象而言,它并不知道应该建立哪个ExportFileApi的实现对象,也不知道如何建立。

也就是说:**对于实现导出数据的业务功能对象,它须要建立ExportFileApi的具体实例对象,可是它只知道ExportFileApi接口,而不知道其具体的实现。**那该怎么办呢?

#2 解决方案# ##2.1 工厂方法模式来解决## 用来解决上述问题的一个合理的解决方案就是工厂方法模式。那么什么是工厂方法模式呢?

  1. 工厂方法模式定义

定义一个用于建立对象的接口,让子类决定实例化哪个类,Factory Method使一个类的实例化延迟到其子类。

  1. 应用工厂方法模式来解决的思路

仔细分析上面的问题,事实上在实现导出数据的业务功能对象里面,根本就不知道究竟要使用哪种导出文件的格式,所以这个对象本就不该该和具体的导出文件的对象耦合在一块儿,它只须要面向导出的文件对象的接口就行了

可是这样一来,又有新的问题产生了:接口是不能直接使用的,须要使用具体的接口实现对象的实例

这不是自相矛盾吗?要求面向接口,不让和具体的实现耦合,可是又须要建立接口的具体实现对象的实例。怎么解决这个矛盾呢?

工厂方法模式的解决思路颇有意思,那就是不解决,采起无为而治的方式:不是须要接口对象吗,那就定义一个方法来建立;但是事实上它本身是不知道如何建立这个接口对象的,没有关系,那就定义成抽象方法就行了,本身实现不了,那就让子类来实现,这样这个对象自己就能够只是面向接口编程,而无需关心到底如何建立接口对象了

##2.2 模式结构和说明## 工厂方法模式的结构如图所示:

输入图片说明

Product:定义工厂方法所建立的对象的接口,也就是实际须要使用的对象的接口。

ConcreteProduct:具体的Product接口的实现对象。

Creator:建立器,声明工厂方法,工厂方法一般会返回一个Product类型的实例对象,并且可能是抽象方法。也能够在Creator里面提供工厂方法的默认实现,让工厂方法返回一个缺省的Product类型的实例对象。

ConcreteCreator:具体的建立器对象,覆盖实现Creator定义的工厂方法,返回具体的Product实例。

##2.3 工厂方法模式示例代码##

  1. 先看看Product的定义,示例代码以下:
/**
 * 工厂方法所建立的对象的接口
 */
public interface Product {
    //能够定义Product的属性和方法
}
  1. 再看看具体的Product的实现对象,示例代码以下:
/**
 * 具体的Product对象
 */
public class ConcreteProduct implements Product {
    //实现Product要求的方法
}
  1. 接下来看看建立器的定义,示例代码以下:
/**
 * 建立器,声明工厂方法
 */
public abstract class Creator {
    /**
     * 建立Product的工厂方法
     * @return Product对象
     */
    protected abstract Product factoryMethod();
    /**
     * 示意方法,实现某些功能的方法
     */
    public void someOperation() {
        //一般在这些方法实现中,须要调用工厂方法来获取Product对象
        Product product = factoryMethod();
    }
}
  1. 再看看具体的建立器实现对象,示例代码以下:
/**
 * 具体的建立器实现对象
 */
public class ConcreteCreator extends Creator {
    protected Product factoryMethod() {
        //重定义工厂方法,返回一个具体的Product对象
        return new ConcreteProduct();
    }
}

##2.4 使用工厂方法模式来实现示例## 要使用工厂方法模式来实现示例,先来按照工厂方法模式的结构,对应出哪些是被建立的Product,哪些是Creator。分析要求实现的功能,导出的文件对象接口ExportFileApi就至关因而Product,而用来实现导出数据的业务功能对象就至关于Creator。把Product和Creator分开事后,就能够分别来实现它们了。

使用工厂模式来实现示例的程序结构如图所示:

输入图片说明

  1. 导出的文件对象接口ExportFileApi的实现没有变化,这里就不去赘述了

  2. **接下来看看接口ExportFileApi的实现,为了示例简单,只实现导出文本文件格式和数据库备份文件两种。先看看导出文本文件格式的实现,示例代码以下: **

/**
 * 导出成文本文件格式的对象
 */
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;
    }
}
  1. Creator这边的实现,首先看看ExportOperate的实现,示例代码以下:
/**
 * 实现导出数据的业务功能对象
 */
public abstract class ExportOperate {
    /**
     * 导出文件
     * @param data 须要保存的数据
     * @return 是否成功导出文件
     */
    public boolean export(String data){
        //使用工厂方法
        ExportFileApi api = factoryMethod();
        return api.export(data);
    }
    /**
     * 工厂方法,建立导出的文件对象的接口对象
     * @return 导出的文件对象的接口对象
     */
    protected abstract ExportFileApi factoryMethod();
}
  1. 加入了两个Creator实现,示例代码以下:
/**
 * 具体的建立器实现对象,实现建立导出成文本文件格式的对象
 */
public class ExportTxtFileOperate extends ExportOperate{
    protected ExportFileApi factoryMethod() {
        //建立导出成文本文件格式的对象
        return new ExportTxtFile();
    }
}

/**
 * 具体的建立器实现对象,实现建立导出成数据库备份文件形式的对象
 */
public class ExportDBOperate extends ExportOperate{
    protected ExportFileApi factoryMethod() {
        //建立导出成数据库备份文件形式的对象
        return new ExportDB();
    }
}
  1. 客户端直接建立须要使用的Creator对象,而后调用相应的功能方法,示例代码以下:
public class Client {
    public static void main(String[] args) {
        //建立须要使用的Creator对象
        ExportOperate operate = new ExportDBOperate();
        //调用输出数据的功能方法
        operate.export("测试数据");
    }
}

#3 模式讲解# ##3.1 认识工厂方法模式##

  1. 模式的功能

工厂方法的主要功能是让父类在不知道具体实现的状况下,完成自身的功能调用,而具体的实现延迟到子类来实现。

这样在设计的时候,不用去考虑具体的实现,须要某个对象,把它经过工厂方法返回就行了,在使用这些对象实现功能的时候仍是经过接口来操做,这很是相似于IoC/DI的思想,这个在后面给你们稍详细点介绍一下。

  1. 实现成抽象类

工厂方法的实现中,一般父类会是一个抽象类,里面包含建立所需对象的抽象方法,这些抽象方法就是工厂方法。

这里要注意一个问题,子类在实现这些抽象方法的时候,一般并非真的由子类来实现具体的功能,而是在子类的方法里面作选择,选择具体的产品实现对象

父类里面,一般会有使用这些产品对象来实现必定的功能的方法,并且这些方法所实现的功能一般都是公共的功能,无论子类选择了何种具体的产品实现,这些方法的功能老是能正确执行。

  1. 实现成具体的类

固然也能够把父类实现成为一个具体的类,这种状况下,一般是在父类中提供获取所需对象的默认实现方法,这样就算没有具体的子类,也可以运行

一般这种状况仍是须要具体的子类来决定具体要如何建立父类所须要的对象。也把这种状况称为工厂方法为子类提供了挂钩,经过工厂方法,可让子类对象来覆盖父类的实现,从而提供更好的灵活性。

  1. 工厂方法的参数和返回

工厂方法的实现中,可能须要参数,以便决定到底选用哪种具体的实现。也就是说经过在抽象方法里面传递参数,在子类实现的时候根据参数进行选择,看看究竟应该建立哪个具体的实现对象

通常工厂方法返回的是被建立对象的接口对象,固然也能够是抽象类或者一个具体的类的实例。

  1. 谁来使用工厂方法建立的对象

这里首先要搞明白一件事情,就是谁在使用工厂方法建立的对象?

事实上,在工厂方法模式里面,应该是Creator中的其它方法在使用工厂方法建立的对象,虽然也能够把工厂方法建立的对象直接提供给Creator外部使用,但工厂方法模式的本意,是由Creator对象内部的方法来使用工厂方法建立的对象,也就是说,工厂方法通常不提供给Creator外部使用。

客户端应该是使用Creator对象,或者是使用由Creator建立出来的对象。对于客户端使用Creator对象,这个时候工厂方法建立的对象,是Creator中的某些方法使用。对于使用那些由Creator建立出来的对象,这个时候工厂方法建立的对象,是构成客户端须要的对象的一部分。分别举例来讲明。

① 客户端使用Creator对象的状况

好比前面的示例,对于“实现导出数据的业务功能对象”的类ExportOperate,它有一个export的方法,在这个方法里面,须要使用具体的“导出的文件对象的接口对象” ExportFileApi,而ExportOperate是不知道具体的ExportFileApi实现的,那么怎么作的呢?就是定义了一个工厂方法,用来返回ExportFileApi的对象,而后export方法会使用这个工厂方法来获取它所须要的对象,而后执行功能

这个时候的客户端是怎么作的呢?这个时候客户端主要就是使用这个ExportOperate的实例来完成它想要完成的功能,也就是客户端使用Creator对象的状况,简单描述这种状况下的代码结构以下:

/**
 * 客户端使用Creator对象的状况下,Creator的基本实现结构
 */
public abstract class Creator {
    /**
     * 工厂方法,通常不对外
     * @return 建立的产品对象
     */
    protected abstract Product factoryMethod();
    /**
     * 提供给外部使用的方法,
     * 客户端通常使用Creator提供的这些方法来完成所须要的功能
     */
    public void someOperation(){
        //在这里使用工厂方法
        Product p = factoryMethod();
    }
}

② 客户端使用由Creator建立出来的对象

另一种是由Creator向客户端返回由“工厂方法建立的对象”来构建的对象,这个时候工厂方法建立的对象,是构成客户端须要的对象的一部分。简单描述这种状况下的代码结构以下:

/**
 * 客户端使用Creator来建立客户端须要的对象的状况下,Creator的基本实现结构
 */
public abstract class Creator {
    /**
     * 工厂方法,通常不对外,建立一个部件对象
     * @return 建立的产品对象,通常是另外一个产品对象的部件
     */
    protected abstract Product1 factoryMethod1();
    /**
     * 工厂方法,通常不对外,建立一个部件对象
     * @return 建立的产品对象,通常是另外一个产品对象的部件
     */
    protected abstract Product2 factoryMethod2();
    /**
     * 建立客户端须要的对象,客户端主要使用产品对象来完成所须要的功能
     * @return 客户端须要的对象
     */
    public Product createProduct(){
        //在这里使用工厂方法,获得客户端所需对象的部件对象
        Product1 p1 = factoryMethod1();
        Product2 p2 = factoryMethod2();
        //工厂方法建立的对象是建立客户端对象所须要的
        Product p = new ConcreteProduct();
        p.setProduct1(p1);
        p.setProduct2(p2);
    
        return p;
    }
}

小结一下:在工厂方法模式里面,客户端要么使用Creator对象,要么使用Creator建立的对象,通常客户端不直接使用工厂方法。固然也能够直接把工厂方法暴露给客户端操做,可是通常不这么作

  1. 工厂方法模式的调用顺序示意图

因为客户端使用Creator对象有两种典型的状况,所以调用的顺序示意图也分作两种状况,先看看客户端使用由Creator建立出来的对象状况的调用顺序示意图,如图所示:

输入图片说明

接下来看看客户端使用Creator对象时候的调用顺序示意图,如图所示:

输入图片说明

##3.2 工厂方法模式与IoC/DI##

IoC——Inversion of Control 控制反转

DI——Dependency Injection 依赖注入

  1. 如何理解IoC/DI

要想理解上面两个概念,就必须搞清楚以下的问题:

参与者都有谁?

依赖:谁依赖于谁?为何须要依赖?

注入:谁注入于谁?到底注入什么?

控制反转:谁控制谁?控制什么?为什么叫反转(有反转就应该有正转了)?

依赖注入和控制反转是同一律念吗?

(1) 参与者都有谁:

通常有三方参与者,一个是某个对象;一个是IoC/DI的容器;另外一个是某个对象的外部资源。

又要名词解释一下,某个对象指的就是任意的、普通的Java对象; IoC/DI的容器简单点说就是指用来实现IoC/DI功能的一个框架程序;对象的外部资源指的就是对象须要的,可是是从对象外部获取的,都统称资源,好比:对象须要的其它对象、或者是对象须要的文件资源等等。

(2) 谁依赖于谁:固然是某个对象依赖于IoC/DI的容器

(3) 为何须要依赖:对象须要IoC/DI的容器来提供对象须要的外部资源

(4) 谁注入于谁:很明显是IoC/DI的容器 注入 某个对象

(5) 到底注入什么:就是注入某个对象所须要的外部资源

(6) 谁控制谁:固然是IoC/DI的容器来控制对象了

(7) 控制什么:主要是控制对象实例的建立

(8) 为什么叫反转:

反转是相对于正向而言的,那么什么算是正向的呢?考虑一下常规状况下的应用程序,若是要在A里面使用C,你会怎么作呢?固然是直接去建立C的对象,也就是说,是在A类中主动去获取所须要的外部资源C,这种状况被称为正向的。那么什么是反向呢?就是A类再也不主动去获取C,而是被动等待,等待IoC/DI的容器获取一个C的实例,而后反向的注入到A类中。

用图例来讲明一下,先看没有IoC/DI的时候,常规的A类使用C类的示意图,如图所示:

输入图片说明

当有了IoC/DI的容器后,A类再也不主动去建立C了,如图所示:

输入图片说明

而是被动等待,等待IoC/DI的容器获取一个C的实例,而后反向的注入到A类中,如图所示:

输入图片说明

(9) 依赖注入和控制反转是同一律念吗?

根据上面的讲述,应该能看出来,依赖注入和控制反转是对同一件事情的不一样描述,从某个方面讲,就是它们描述的角度不一样。依赖注入是从应用程序的角度在描述,能够把依赖注入描述完整点:应用程序依赖容器建立并注入它所须要的外部资源;而控制反转是从容器的角度在描述,描述完整点:容器控制应用程序,由容器反向的向应用程序注入应用程序所须要的外部资源。

(10) 小结一下:

其实IoC/DI对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序本来是老大,要获取什么资源都是主动出击,可是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC/DI容器来建立并注入它所须要的资源了。

这么小小的一个改变实际上是编程思想的一个大进步,这样就有效的分离了对象和它所须要的外部资源,使得它们松散耦合,有利于功能复用,更重要的是使得程序的整个体系结构变得很是灵活。

  1. 工厂方法模式和IoC/DI有什么关系呢?

从某个角度讲,它们的思想很相似。

上面讲了,有了IoC/DI事后,应用程序就再也不主动了,而是被动等待由容器来注入资源,那么在编写代码的时候,一旦要用到外部资源,就会开一个窗口,让容器能注入进来,也就是提供给容器使用的注入的途径,固然这不是咱们的重点,就不去细细讲了,用setter注入来示例一下,看看使用IoC/DI的代码是什么样子,示例代码以下:

public class A {
    /**
     * 等待被注入进来
     */
    private C c = null;
    /**
     * 注入资源C的方法
     * @param c 被注入的资源
     */
    public void setC(C c){
        this.c = c;
    }
    public void t1(){
        //这里须要使用C,但是又不让主动去建立C了,怎么办?
        //反正就要求从外部注入,这样更省心,
        //本身不用管怎么获取C,直接使用就行了
        c.tc();
    }
}

接口C的示例代码以下:

public interface C {
    public void tc();
}

从上面的示例代码能够看出,如今在A里面写代码的时候,凡是碰到了须要外部资源,那么就提供注入的途径,要求从外部注入,本身只管使用这些对象。

public abstract class A1 {
    /**
     * 工厂方法,建立C1,相似于从子类注入进来的途径
     * @return C1的对象实例
     */
    protected abstract C1 createC1();
    public void t1(){
        //这里须要使用C1类,但是不知道到底是用哪个
        //也就不主动去建立C1了,怎么办?
        //反正会在子类里面实现,这里不用管怎么获取C1,直接使用就行了
        createC1().tc();
    }
}

子类的示例代码以下:

public class A2 extends A1 {
    protected C1 createC1() {
        //真正的选择具体实现,并建立对象
        return new C2();
    }
}

C1接口和前面C接口是同样的,C2这个实现类也是空的,只是演示一下,所以就不去展现它们的代码了。

仔细体会上面的示例,对比它们的实现,尤为是从思想层面上,会发现工厂方法模式和IoC/DI的思想是类似的,都是“主动变被动”,进行了“主从换位”,从而得到了更灵活的程序结构。

##3.3 平行的类层次结构##

  1. 什么是平行的类层次结构呢?

简单点说,假若有两个类层次结构,其中一个类层次中的每一个类在另外一个类层次中都有一个对应的类的结构,就被称为平行的类层次结构。

举个例子来讲,硬盘对象有不少种,如分红台式机硬盘和笔记本硬盘,在台式机硬盘的具体实现上面,又有希捷、西数等不一样品牌的实现,一样在笔记本硬盘上,也有希捷、日立、IBM等不一样品牌的实现;硬盘对象具备本身的行为,如硬盘能存储数据,也能从硬盘上获取数据,不一样的硬盘对象对应的行为对象是不同的,由于不一样的硬盘对象,它的行为的实现方式是不同的。若是把硬盘对象和硬盘对象的行为分开描述,那么就构成了如图所示的结构:

输入图片说明

硬盘对象是一个类层次,硬盘的行为这边也是一个类层次,并且两个类层次中的类是对应的。台式机西捷硬盘对象就对应着硬盘行为里面的台式机西捷硬盘的行为;笔记本IBM硬盘就对应着笔记本IBM硬盘的行为,这就是一种典型的平行的类层次结构。

这种平行的类层次结构用来干什么呢?主要用来把一个类层次中的某些行为分离出来,让类层次中的类把本来属于本身的职责,委托给分离出来的类去实现,从而使得类层次自己变得更简单,更容易扩展和复用。

通常来说,分离出去的这些类的行为,会对应着类层次结构来组织,从而造成一个新的类层次结构,至关于原来对象的行为的这么一个类层次结构,而这个层次结构和原来的类层次结构是存在对应关系的,所以被称为平行的类层次结构。

  1. 工厂方法模式跟平行的类层次结构有何关系呢?

可使用工厂方法模式来链接平行的类层次。

看上面的示例图,在每一个硬盘对象里面,都有一个工厂方法createHDOperate,经过这个工厂方法,客户端就能够获取一个跟硬盘对象相对应的行为对象。在硬盘对象的子类里面,会覆盖父类的工厂方法createHDOperate,以提供跟自身相对应的行为对象,从而天然的把两个平行的类层次链接起来使用。

##3.4 参数化工厂方法## 所谓参数化工厂方法指的就是:经过给工厂方法传递参数,让工厂方法根据参数的不一样来建立不一样的产品对象,这种状况就被称为参数化工厂方法。固然工厂方法建立的不一样的产品必须是同一个Product类型的。

来改造前面的示例,如今有一个工厂方法来建立ExportFileApi这个产品的对象,可是ExportFileApi接口的具体实现不少,为了方便建立的选择,直接从客户端传入一个参数,这样在须要建立ExportFileApi对象的时候,就把这个参数传递给工厂方法,让工厂方法来实例化具体的ExportFileApi实现对象。

  1. 先来看Product的接口,就是ExportFileApi接口,跟前面的示例没有任何变化,为了方便你们查看,这里重复一下,示例代码以下:
/**
 * 导出的文件对象的接口
 */
public interface ExportFileApi {
    /**
     * 导出内容成为文件
     * @param data 示意:须要保存的数据
     * @return 是否导出成功
     */
    public boolean export(String data); 
}
  1. 一样提供保存成文本文件和保存成数据库备份文件的实现,跟前面的示例没有任何变化,示例代码以下:
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;
    }
}
  1. 接下来该看看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;
    }
}
  1. 此时的客户端,很是简单,直接使用ExportOperate类,示例代码以下:
public class Client {
    public static void main(String[] args) {
        //建立须要使用的Creator对象
        ExportOperate operate = new ExportOperate();
        //调用输出数据的功能方法,传入选择处处类型的参数
        operate.export(1,"测试数据");
    }
}

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

  1. 扩展新的实现

使用参数化工厂方法,扩展起来会很是容易,已有的代码都不会改变,只要新加入一个子类来提供新的工厂方法实现,而后在客户端使用这个新的子类便可。

这种实现方式还有一个有意思的功能,就是子类能够选择性覆盖,不想覆盖的功能还能够返回去让父类来实现,颇有意思。

先扩展一个导出成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");
    }
}

##3.5 工厂方法模式的优缺点##

  1. 能够在不知具体实现的状况下编程

工厂方法模式可让你在实现功能的时候,若是须要某个产品对象,只须要使用产品的接口便可,而无需关心具体的实现。选择具体实现的任务延迟到子类去完成。 更容易扩展对象的新版本。

工厂方法给子类提供了一个挂钩,使得扩展新的对象版本变得很是容易。好比上面示例的参数化工厂方法实现中,扩展一个新的导出Xml文件格式的实现,已有的代码都不会改变,只要新加入一个子类来提供新的工厂方法实现,而后在客户端使用这个新的子类便可。

另外这里提到的挂钩,就是咱们常常说的钩子方法(hook),这个会在后面讲模板方法模式的时候详细点说明。

  1. 链接平行的类层次

工厂方法除了创造产品对象外,在链接平行的类层次上也大显身手。这个在前面已经详细讲述了。

  1. 具体产品对象和工厂方法的耦合性

在工厂方法模式里面,工厂方法是须要建立产品对象的,也就是须要选择具体的产品对象,并建立它们的实例,所以具体产品对象和工厂方法是耦合的。

##3.6 思考工厂方法模式##

  1. 工厂方法模式的本质

工厂方法模式的本质:延迟到子类来选择实现。

仔细体会前面的示例,你会发现,工厂方法模式中的工厂方法,在真正实现的时候,通常是先选择具体使用哪个具体的产品实现对象,而后建立这个具体产品对象的示例,而后就能够返回去了。也就是说,工厂方法自己并不会去实现产品接口,具体的产品实现是已经写好了的,工厂方法只要去选择实现就行了。

有些朋友可能会说,这不是跟简单工厂同样吗?

确实从本质上讲,它们是很是相似的,具体实现上都是在“选择实现”。可是也存在不一样点,简单工厂是直接在工厂类里面进行“选择实现”;而工厂方法会把这个工做延迟到子类来实现,工厂类里面使用工厂方法的地方是依赖于抽象而不是具体的实现,从而使得系统更加灵活,具备更好的可维护性和可扩展性。

其实若是把工厂模式中的Creator退化一下,只提供工厂方法,并且这些工厂方法还都提供默认的实现,那不就变成了简单工厂了吗?好比把刚才示范参数化工厂方法的例子代码拿过来再简化一下,你就能看出来,写得跟简单工厂是差很少的,示例代码以下:

输入图片说明

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

  1. 对设计原则的体现

工厂方法模式很好的体现了“依赖倒置原则”。

依赖倒置原则告诉咱们“要依赖抽象,不要依赖于具体类”,简单点说就是:不能让高层组件依赖于低层组件,并且无论高层组件仍是低层组件,都应该依赖于抽象。

好比前面的示例,实现客户端请求操做的ExportOperate就是高层组件;而具体实现数据导出的对象就是低层组件,好比ExportTxtFile、ExportDB;而ExportFileApi接口就至关因而那个抽象。

对于ExportOperate来讲,它不关心具体的实现方式,它只是“面向接口编程”;对于具体的实现来讲,它只关心本身“如何实现接口”所要求的功能。

那么倒置的是什么呢?倒置的是这个接口的“全部权”。事实上,ExportFileApi接口中定义的功能,都是由高层组件ExportOperate来提出的要求,也就是说接口中的功能,是高层组件须要的功能。可是高层组件只是提出要求,并不关心如何实现,而低层组件,就是来真正实现高层组件所要求的接口功能的。所以看起来,低层实现的接口的全部权并不在底层组件手中,而是倒置到高层组件去了

  1. 什么时候选用工厂方法模式

建议在以下状况中,选用工厂方法模式:

若是一个类须要建立某个接口的对象,可是又不知道具体的实现,这种状况能够选用工厂方法模式,把建立对象的工做延迟到子类去实现

若是一个类自己就但愿,由它的子类来建立所需的对象的时候,应该使用工厂方法模式

##3.7 相关模式##

  1. 工厂方法模式和抽象工厂模式

这两个模式能够组合使用,具体的放到抽象工厂模式中去讲。

  1. 工厂方法模式和模板方法模式

这两个模式外观相似,都是有一个抽象类,而后由子类来提供一些实现,可是工厂方法模式的子类专一的是建立产品对象,而模板方法模式的子类专一的是为固定的算法骨架提供某些步骤的实现

这两个模式能够组合使用,一般在模板方法模式里面,使用工厂方法来建立模板方法须要的对象。

相关文章
相关标签/搜索