1. 概述
先来说一下生活中的场景。我们在组装电脑的时候通常都会选一系列的配件,如CPU、硬盘、内存和主板等等。这次讨论只考虑CPU和主板的问题。
在选择配件的时候,我们都会参考一系列参数。比如选CPU的时候会考虑品牌、型号、针脚数目和主频等参数,选主板的时候会考虑品牌、芯片组和集成芯片等参数。
在考虑完单个组件的问题后,还需要考虑一下配件与配件之间的兼容性。比如CPU和主板,如果CPU的针脚数和主板提供的CPU插口不兼容,是无法组装的。
对于装机工程师而言,他只知道要组装一台电脑,需要相应的配件,但是具体怎么样的配件还是得由客户说了算。也就是说装机工程师只是负责组装,而客户负责选择装配所需要的具体配件。
上面这个生活中的场景,可以用代码实现模拟一下,我们可以用到抽象工厂模式来解决兼容性问题。下面先来介绍一下抽象工厂模式,然后再来应用到这个场景中。
2. 抽象工厂模式介绍
抽象工厂模式的定义
提供一个创建一系列相关或互相依赖对象的接口,而无需指定它们具体的类。
抽象工厂模式的结构和说明
抽象工厂模式的结构如下图所示
- Abstract Factory:抽象工厂,定义创建一系列产品对象的操作接口。
- Concrete Factory:具体的工厂,实现抽象工厂定义的方法,具体实现一系列产品对象的创建。
- Abstract Product:定义一类产品对象接口。
- Concrete Product:具体的产品实现对象,通常在具体工厂里面,会选择具体产品实现对象,来创建符合抽象工厂定义的方法返回的产品类型的对象。
- Client:客户端,主要使用抽象工厂来获取一系列所需要的产品对象,然后面向这些产品对象的接口编程,以实现需要的功能。
抽象工厂模式示例代码
(1)抽象工厂的定义,代码如下:
/**
* 抽象工厂的接口,声明创建抽象产品对象的操作
*/
public interface AbstractFactory {
/**
* 示例方法,创建抽象产品A的对象
* @return 抽象产品A的对象
*/
public AbstractProductA createProductA();
/**
* 示例方法,创建抽象产品B的对象
* @return 抽象产品B的对象
*/
public AbstractProductB createProductB();
}
(2)接下来看看产品的定义,只是示意一下,没有具体的方法和属性,代码如下:
/**
* 抽象产品A的接口
*/
public interface AbstractProductA {
//定义抽象产品A相关的操作
}
/**
* 抽象产品A的接口
*/
public interface AbstractProductA {
//定义抽象产品A相关的操作
}
(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 ProductB1 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();
}
}
到此,抽象工厂模式的实现就完成了。但是这样子看总觉得有点虚,而且优缺点也不好看出,所以下面就要应用到刚开始的组装电脑的场景中,加强一下理解。
3. 使用抽象工厂模式
代码实现
好了,下面就要用代码来描述一下现实世界发生的事情。
(1)下面来看看CPU和主板的接口
CPU接口的定义代码如下:
/**
* CPU的接口
*/
public interface CPUApi {
/**
* 示意方法,CPU具有运算的功能
*/
public void calculate();
}
主板接口的定义代码如下:
/**
* 主板的接口
*/
public interface MainboardApi {
/**
* 示意方法,主板都具有安装CPU的功能
*/
public void installCPU();
}
(2)再来看看具体的CPU实现。
Intel的CPU实现代码如下:
/**
*Intel的CPU实现
*/
public class IntelCPU implements CPUApi{
/**
* CPU的针脚数目
*/
private int pins = 0;
/**
* 构造方法,传入CPU的针脚数目
* @param pins CPU的针脚数目
*/
public IntelCPU(int pins){
this.pins = pins;
}
public void calculate() {
System.out.println("now in Intel CPU,pins="+pins);
}
}
AMD的CPU实现代码如下:
/**
* AMD的CPU实现
*/
public class AMDCPU implements CPUApi{
/**
* CPU的针脚数目
*/
private int pins = 0;
/**
* 构造方法,传入CPU的针脚数目
* @param pins CPU的针脚数目
*/
public AMDCPU(int pins){
this.pins = pins;
}
public void calculate() {
System.out.println("now in AMD CPU,pins="+pins);
}
}
(3)下面来看看具体的主板实现。
技嘉主板实现代码如下:
/**
* 技嘉的主板
*/
public class GAMainboard implements MainboardApi {
/**
* CPU插槽的孔数
*/
private int cpuHoles = 0;
/**
* 构造方法,传入CPU插槽的孔数
* @param cpuHoles CPU插槽的孔数
*/
public GAMainboard(int cpuHoles){
this.cpuHoles = cpuHoles;
}
public void installCPU() {
System.out.println("now in GAMainboard,cpuHoles="+cpuHoles);
}
}
微星主板实现代码如下:
/**
* 微星的主板
*/
public class MSIMainboard implements MainboardApi{
/**
* CPU插槽的孔数
*/
private int cpuHoles = 0;
/**
* 构造方法,传入CPU插槽的孔数
* @param cpuHoles CPU插槽的孔数
*/
public MSIMainboard(int cpuHoles){
this.cpuHoles = cpuHoles;
}
public void installCPU() {
System.out.println("now in MSIMainboard,cpuHoles="+cpuHoles);
}
}
(4)下面就来看看抽象工厂类的代码:
/**
* 抽象工厂的接口,声明创建抽象产品对象的操作
*/
public interface AbstractFactory {
/**
* 示例方法,创建抽象产品A的对象
* @return 抽象产品A的对象
*/
public AbstractProductA createProductA();
/**
* 示例方法,创建抽象产品B的对象
* @return 抽象产品B的对象
*/
public AbstractProductB createProductB();
}
(5)再看看抽象工厂的实现对象,也就是具体的装机方案对象。
先看看装机方案一的实现:
/**
* 装机方案一: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);
}
}
(6)下面来看看装机工程师类的实现。装机工程师相当于抽象工厂的客户端,虽然是由真正的客户来选择创建具体的工厂对象,但是使用抽象工厂的是装机工程师对象。
装机工程师接收客户传进来的装机方案进行装机,这样子就避免了单独去选择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();
}
}
(7)准备工作完毕,现在就来看看客户端的使用代码:
public class Client {
public static void main(String[] args) {
//创建装机工程师对象
ComputerEngineer engineer = new ComputerEngineer();
//客户选择并创建需要使用的装机方案对象
AbstractFactory schema = new Schema1();
//告诉装机工程师自己选择的装机方案,让装机工程师组装电脑
engineer.makeComputer(schema);
}
}
最后控制台中打印出下面内容
now in Intel CPU,pins=1156
now in GAMainboard,cpuHoles=1156
例子结构
可能类有点多,大家看的有点懵,看一下下面这张结构图让大家更好理解一下
4. 模式讲解
抽象工厂模式的功能
抽象工厂的功能是为一系列相关对象或相互依赖的对象创建一个接口。一定要注意,这个接口内的方法不是任意堆砌的,而是一系列相关或相互依赖的方法,比如上面例子中CPU和主板,都是为了组装一台电脑的相关对象。
从某种意义上来看,抽象工厂其实是一个产品系列,或者是产品簇。上面例子中的抽象工厂就可以看成是电脑簇,每个不同的装机方案,都代表一种具体的电脑系列。
实现成接口
AbstractFactory在Java中通常实现成为接口,大家不要被名称误导了,以为是实现成为抽象类。当然,如果需要为这个产品簇提供公共的功能,也不是不可以把AbstractFactory实现成为抽象类,但一般不这么做。
使用工厂方法
AbstractFactory定义了创建产品所需要的接口,具体的实现是在实现类里面,通常在实现类里面就需要选择多种更具体的实现,所以AbstractFactory定义的创建产品的方法可以看成是工厂方法,而这些工厂方法的具体实现就延迟到了具体的工厂里面。也就是说使用工厂方法来实现抽象工厂。
切换产品簇
由于抽象工厂定义的一系列对象,通常是相关或者相依赖的,这些产品对象就构成了一个产品簇,也就是抽象工厂定义了一个产品簇。
这就带来非常大的灵活性,切换一个产品簇的时候,只要提供不同的抽象工厂实现就好了,也就是说现在是以产品簇做为一个整体被切换。
抽象工厂模式的调用顺序示意图
5. 抽象工厂模式的优缺点
优点
- 分离接口和实现:
客户端使用抽象工厂来创建需要的对象,而客户端根本就不知道具体的实现是谁,客户端只是面向产品的接口编程而已,也就是说,客户端从具体的产品实现中解耦。 - 使得切换产品簇变得容易
因为一个具体的工厂实现代表的是一个产品簇,比如上面例子的Scheme1代表装机方案一:Intel 的CPU + 技嘉的主板,如果要切换成为Scheme2,那就变成了装机方案二:AMD的CPU + 微星的主板。
客户端选用不同的工厂实现,就相当于是在切换不同的产品簇。
缺点
- 不太容易扩展新的产品
如果需要给整个产品簇添加一个新的产品,那么就需要修改抽象工厂,这样就会导致修改所有的工厂实现类。 - 容易造成类层次复杂
在使用抽象工厂模式的时候,如果需要选择的层次过多,那么会造成整个类层次变得复杂。比如上面例子中如果CPU和主板的搭配选择太多,那么客户端怎么选择呢?不会把所有可能的实现情况全部都做到一个层次上吧,这个时候客户端就需要一层一层选择,也就是整个抽象工厂的实现也需要分出层次来,每一层负责一种选择,也就是一层屏蔽一种变化,这样很容易造成复杂的类层次结构。
以上内容参考至《研磨设计模式》
如果上面的内容有错误的地方或者讲的不好的地方,还请大家指点一下,我好及时修改。