7.2 解 决 方 案

7.2.1 使用抽象工厂模式来解决问题

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

1. 抽象工厂模式的定义

提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

2. 应用抽象工厂模式来解决问题的思路

仔细分析上面的问题,其实有两个问题点,一个是只知道所需要的一系列对象的接口,而不知具体实现,或者是不知道具体使用哪一个实现;另外一个是这一系列对象是相关或者相互依赖的,也就是说既要创建接口的对象,还要约束它们之间的关系。

有朋友可能会想,工厂方法模式或者是简单工厂,不就可以解决只知接口而不知实现的问题吗?怎么这些问题又冒出来了呢?

注意

请注意,这里要解决的问题和工厂方法模式或简单工厂解决的问题是有很大不同的,工厂方法模式或简单工厂关注的是单个产品对象的创建,比如创建CPU的工厂方法,它就只关心如何创建CPU的对象,而创建主板的工厂方法,就只关心如何创建主板对象。

这里要解决的问题是,要创建一系列的产品对象,而且这一系列对象是构建新的对象所需要的组成部分,也就是这一系列被创建的对象相互之间是有约束的。

解决这个问题的一个解决方案就是抽象工厂模式。在这个模式里面,会定义一个抽象工厂,在里面虚拟地创建客户端需要的这一系列对象,所谓虚拟的就是定义创建这些对象的抽象方法,并不去真正地实现,然后由具体的抽象工厂的子类来提供这一系列对象的创建。这样一来可以为同一个抽象工厂提供很多不同的实现,那么创建的这一系列对象也就不一样了,也就是说,抽象工厂在这里起到一个约束的作用,并提供所有子类的一个统一外观,来让客户端使用。

7.2.2 抽象工厂模式的结构和说明

抽象工厂模式的结构如图7.1所示。

图片

图7.1 抽象工厂模式的结构示意图

■ Abstract Factory:抽象工厂,定义创建一系列产品对象的操作接口。

■ Concrete Factory:具体的工厂,实现抽象工厂定义的方法,具体实现一系列产品对象的创建。

■ Abstract Product:定义一类产品对象的接口。

■ Concrete Product:具体的产品实现对象,通常在具体工厂里面,会选择具体的产品实现对象,来创建符合抽象工厂定义的方法返回的产品类型的对象。

■ Client:客户端,主要使用抽象工厂来获取一系列所需要的产品对象,然后面向这些产品对象的接口编程,以实现需要的功能。

7.2.3 抽象工厂模式示例代码

(1)先看看抽象工厂的定义。示例代码如下:

5fe735a61eef46ebb4b92a1b2b4a95cd

(2)接下来看看产品的定义,由于只是示意,并没有去定义具体的方法,示例代码如下:

f444e648519247ca9d729a7b6429dbe9

4c8244375ac04c09ba39f1d375a8ee38

(3)同样的,产品的各个实现对象也是空的。

实现产品A示例代码如下:

3627102113164a899f85987266514a54

实现产品B的示例代码如下:

a3fccc2f5b8f45b097cd1d33259eee97

(4)再来看看具体的工厂的实现示意。示例代码如下:

4d6c5f2cde3646f9b718ed7454b991c4

d241c34cbaae4d88abf9c30074297f91

(5)实现客户端的示例代码如下:

cb8aff48d7cd40af92d40a4545d16e3a

7.2.4 使用抽象工厂模式重写示例

要使用抽象工厂模式来重写示例,先来看看如何使用抽象工厂模式来解决前面提出的问题。

装机工程师要组装电脑对象,需要一系列的产品对象,比如CPU、主板等,于是创建一个抽象工厂给装机工程师使用,在这个抽象工厂里面定义抽象地创建CPU和主板的方法,这个抽象工厂就相当于一个抽象的装机方案,在这个装机方案里面,各个配件是能够相互匹配的。

每个装机的客户,会提出他们自己的具体装机方案,或者是选择已有的装机方案,相当于为抽象工厂提供了具体的子类,在这些具体的装机方案类里面,会创建具体的CPU和主板实现对象。

此时系统的结构如图7.2所示。

图片

图7.2 抽象工厂重写示例的结构示意图

虽然说是重写示例,但并不是前面写的都不要了,而是修改前面的示例,使它能更好地实现需要的功能。

(1)前面示例实现的CPU接口和CPU实现对象,还有主板的接口和实现对象,都不需要变化,这里就不再赘述了。

(2)前面示例中创建CPU的简单工厂和创建主板的简单工厂,都不再需要了,直接删除即可,这里也就不去管它了。

(3)看看新加入的抽象工厂的定义。示例代码如下:

a85461f8b6fb444bbf9897c4b27e078f

757a361731d14758949ef2edf0865e5c

(4)再看看抽象工厂的实现对象,也就是具体的装机方案对象。

先看看装机方案一的实现。示例代码如下:

684b21abb5c04a889fce3334d914447d

再看看装机方案二的实现。示例代码如下:

f3eaa6dc358742d1b75d73fb756fa585

(5)下面来看看装机工程师类的实现。在现在的实现里面,装机工程师相当于使用抽象工厂的客户端,虽然是由真正的客户来选择和创建具体的工厂对象,但是使用抽象工厂的是装机工程师对象。

装机工程师类跟前面的实现相比,主要的变化是:从客户端不再传入选择CPU和主板的参数,而是直接传入客户选择并创建好的装机方案对象。这样就避免了单独去选择CPU和主板,客户要选就是一套,就是一个系列。示例代码如下:

794105c25bba4d07873248b861f917d5

03ff8d684d484649ab56fbfeed70a16c

caadb8b618a741559aabdf613d6f20ad

(6)都定义好了,下面看看客户端如何使用抽象工厂。示例代码如下:

871a45e370a548caa0024db918ae8033

运行一下,测试看看,是否能满足功能的要求。

如同前面的示例,定义了一个抽象工厂AbstractFactory,在里面定义了创建CPU和主板对象的接口的方法,但是在抽象工厂里面,并没有指定具体的CPU和主板的实现,也就是无须指定它们具体的实现类。

CPU和主板是相关的对象,是构建电脑的一系列相关配件,这个抽象工厂就相当于一个装机方案,客户选择装机方案的时候,一选就是一套,CPU和主板是确定好的,不让客户分开选择,这就避免了出现不匹配的错误。