用来解决上述问题的一个合理的解决方案就是抽象工厂模式。那么什么是抽象工厂模式呢?
(1)抽象工厂模式定义
提供一个创建一系列相关或相互依赖的接口,而无需指定它们的具体类
(2)应用抽象工厂模式来解决的思路
仔细分析上面的问题,其实有两个问题点,一个是只知道所需要的一系列对象的接口,而不知具体实现,或者是不知道具体使用哪一个实现;另外一个是这一系列对象是相关或者相互依赖的。也就是说既要创建接口的对象,还要约束它们之间的关系。
有朋友可能会想,工厂方法模式或者是简单工厂,不就可以解决只知接口而不知实现的问题吗?怎么这些问题又冒出来了呢?
请注意,这里要解决的问题和工厂方法模式或简单工厂解决的问题是有很大不同的,工厂方法模式或简单工厂关注的是单个产品对象的创建,比如创建CPU的工厂方法,它就只关心如何创建CPU的对象,而创建主板的工厂方法,就只关心如何创建主板对象。
这里要解决的问题是,要创建一系列的产品对象,而且这一系列对象是构建新的对象所需要的组成部分,也就是这一系列被创建的对象相互之间是有约束的。
解决这个问题的一个解决方案就是抽象工厂模式。在这个模式里面,会定义一个抽象工厂,在里面虚拟的创建客户端需要的这一系列对象,所谓虚拟的就是定义创建这些对象的抽象方法,并不去真的实现,然后由具体的抽象工厂的子类来提供这一系列对象的创建。这样一来可以为同一个抽象工厂提供很多不同的实现,那么创建的这一系列对象也就不一样了,也就是说,抽象工厂在这里起到一个约束的作用,并提供所有子类的一个统一外观,来让客户端使用。
7.2.2 模式结构和说明
抽象工厂模式结构如图7.1所示:
图7.1 抽象工厂模式结构示意图
AbstractFactory:
抽象工厂,定义创建一系列产品对象的操作接口。
ConcreteFactory:
具体的工厂,实现抽象工厂定义的方法,具体实现一系列产品对象的创建。
AbstractProduct:
定义一类产品对象的接口。
ConcreteProduct:
具体的产品实现对象,通常在具体工厂里面,会选择具体的产品实现对象,来创建符合抽象工厂定义的方法返回的产品类型的对象。
Client:
客户端,主要使用抽象工厂来获取一系列所需要的产品对象,然后面向这些产品对象的接口编程,以实现需要的功能。
7.2.3 抽象工厂模式示例代码
(1)先看看抽象工厂的定义,示例代码如下:
- /**
- * 抽象工厂的接口,声明创建抽象产品对象的操作
- */
- public interface AbstractFactory {
- /**
- * 示例方法,创建抽象产品A的对象
- * @return 抽象产品A的对象
- */
- public AbstractProductA createProductA();
- /**
- * 示例方法,创建抽象产品B的对象
- * @return 抽象产品B的对象
- */
- public AbstractProductB createProductB();
- }
(2)接下来看看产品的定义,由于只是示意,并没有去定义具体的方法,示例代码如下:
- /**
- * 抽象产品A的接口
- */
- public interface AbstractProductA {
- //定义抽象产品A相关的操作
- }
- /**
- * 抽象产品B的接口
- */
- public interface AbstractProductB {
- //定义抽象产品B相关的操作
- }
(3)同样的,产品的各个实现对象也是空的,产品A的具体实现,示例代码如下:
- /**
- * 产品A的具体实现
- */
- public class ProductA1 implements AbstractProductA {
- //实现产品A的接口中定义的操作
- }
- /**
- * 产品A的具体实现
- */
- public class ProductA2 implements AbstractProductA {
- //实现产品A的接口中定义的操作
- }
产品B的具体实现,示例代码如下:
- /**
- * 产品B的具体实现
- */
- public class ProductB1 implements AbstractProductB {
- //实现产品B的接口中定义的操作
- }
- /**
- * 产品B的具体实现
- */
- public class ProductB2 implements AbstractProductB {
- //实现产品B的接口中定义的操作
- }
(4)接下来看看具体的工厂的实现示意,示例代码如下:
- /**
- * 具体的工厂实现对象,实现创建具体的产品对象的操作
- */
- public class ConcreteFactory1 implements AbstractFactory {
- public AbstractProductA createProductA() {
- return new ProductA1();
- }
- public AbstractProductB createProductB() {
- return new ProductB1();
- }
- }
- /**
- * 具体的工厂实现对象,实现创建具体的产品对象的操作
- */
- public class ConcreteFactory2 implements AbstractFactory {
- public AbstractProductA createProductA() {
- return new ProductA2();
- }
- public AbstractProductB createProductB() {
- return new ProductB2();
- }
- }
(5)最后来看看客户端的实现示意,示例代码如下:
- public class Client {
- public static void main(String[] args) {
- //创建抽象工厂对象
- AbstractFactory af = new ConcreteFactory1();
- //通过抽象工厂来获取一系列的对象,如产品A和产品B
- af.createProductA();
- af.createProductB();
- }
- }
7.2.4 使用抽象工厂模式重写示例
要使用抽象工厂模式来重写示例,先来看看如何使用抽象工厂模式来解决前面提出的问题。
装机工程师要组装电脑对象,需要一系列的产品对象,比如CPU、主板等,于是创建一个抽象工厂给装机工程师使用,在这个抽象工厂里面定义抽象的创建CPU和主板的方法,这个抽象工厂就相当于一个抽象的装机方案,在这个装机方案里面,各个配件是能够相互匹配的。
每个装机的客户,会提出他们自己的具体装机方案,或者是选择已有的装机方案,相当于为抽象工厂提供了具体的子类,在这些具体的装机方案类里面,会创建具体的CPU和主板实现对象。
此时系统的结构如图7.2所示:
图7.2 抽象工厂重写示例的结构示意图
虽然说是重写示例,但并不是前面写的都不要了,而是修改前面的示例,使它能更好的实现需要的功能。
(1)前面示例实现的CPU接口和CPU实现对象,还有主板的接口和实现对象,都不需要变化,这里就不去赘述了。
(2)前面示例中的创建CPU的简单工厂和创建主板的简单工厂,都不再需要了,直接删除即可,这里也就不去管了。
(3)看看新加入的抽象工厂的定义,示例代码如下:
(4)再看看抽象工厂的实现对象,也就是具体的装机方案对象,先看看装机方案一的实现,示例代码如下:
- /**
- * 抽象工厂的接口,声明创建抽象产品对象的操作
- */
- public interface AbstractFactory {
- /**
- * 创建CPU的对象
- * @return CPU的对象
- */
- public CPUApi createCPUApi();
- /**
- * 创建主板的对象
- * @return 主板的对象
- */
- public MainboardApi createMainboardApi();
- }
- /**
- * 装机方案一:Intel 的CPU + 技嘉的主板
- * 这里创建CPU和主板对象的时候,是对应的,能匹配上的
- */
- public class Schema1 implements AbstractFactory{
- public CPUApi createCPUApi() {
- return new IntelCPU(1156);
- }
- public MainboardApi createMainboardApi() {
- return new GAMainboard(1156);
- }
- }
再看看装机方案二的实现,示例代码如下:
- /**
- * 装机方案二:AMD的CPU + 微星的主板
- * 这里创建CPU和主板对象的时候,是对应的,能匹配上的
- */
- public class Schema2 implements AbstractFactory{
- public CPUApi createCPUApi() {
- return new AMDCPU(939);
- }
- public MainboardApi createMainboardApi() {
- return new MSIMainboard(939);
- }
- }
(5)再来看看装机工程师类的实现,在现在的实现里面,装机工程师相当于使用抽象工厂的客户端,虽然是由真正的客户来选择和创建具体的工厂对象,但是使用抽象工厂的是装机工程师对象。
装机工程师类跟前面的实现相比,主要的变化是:从客户端,不再传入选择CPU和主板的参数,而是直接传入客户选择并创建好的装机方案对象。这样就避免了单独去选择CPU和主板,客户要选就是一套,就是一个系列。示例代码如下:
- /**
- * 装机工程师的类
- */
- public class ComputerEngineer {
- /**
- * 定义组装机器需要的CPU
- */
- private CPUApi cpu= null;
- /**
- * 定义组装机器需要的主板
- */
- private MainboardApi mainboard = null;
- /**
- * 装机过程
- * @param schema 客户选择的装机方案
- */
- public void makeComputer(AbstractFactory schema){
- //1:首先准备好装机所需要的配件
- prepareHardwares(schema);
- //2:组装机器
- //3:测试机器
- //4:交付客户
- }
- /**
- * 准备装机所需要的配件
- * @param schema 客户选择的装机方案
- */
- private void prepareHardwares(AbstractFactory schema){
- //这里要去准备CPU和主板的具体实现,为了示例简单,这里只准备这两个
- //可是,装机工程师并不知道如何去创建,怎么办呢?
- //使用抽象工厂来获取相应的接口对象
- this.cpu = schema.createCPUApi();
- this.mainboard = schema.createMainboardApi();
- //测试一下配件是否好用
- this.cpu.calculate();
- this.mainboard.installCPU();
- }
- }
(6)都定义好了,看看客户端如何使用抽象工厂,示例代码如下:
- public class Client {
- public static void main(String[] args) {
- //创建装机工程师对象
- ComputerEngineer engineer = new ComputerEngineer();
- //客户选择并创建需要使用的装机方案对象
- AbstractFactory schema = new Schema1();
- //告诉装机工程师自己选择的装机方案,让装机工程师组装电脑
- engineer.makeComputer(schema);
- }
- }
去运行一下,测试看看,是否能满足功能的要求。
如同前面的示例,定义了一个抽象工厂AbstractFactory,在里面定义了创建CPU和主板对象的接口的方法,但是在抽象工厂里面,并没有指定具体的CPU和主板的实现,也就是无须指定它们具体的实现类。
CPU和主板是相关的对象,是构建电脑的一系列相关配件,这个抽象工厂就相当于一个装机方案,客户选择装机方案的时候,一选就是一套,CPU和主板是确定好的,不让客户分开选择,这就避免了出现不匹配的错误。