抽象工厂模式 (Abstract Factory)
场景问题
问题案例
对于装机工程师而言,他只知道组装一台电脑,需要相应的配件,但是具体使用什么样的配件,还得由客户说了算。也就是说装机工程师只是负责组装,而客户负责选择装配所需要的具体的配件。因此,当装机工程师为不同的客户组装电脑时,只需要按照客户的装机方案,去获取相应的配件,然后组装即可。
不用模式的解决方案
【CPU定义】
/**
* CPU的类型
*/
public enum CPUType {
INTEL, AMD
}
/**
* CPU的接口
*/
public interface CPUApi {
/**
* 示意方法,CPU具有运算的功能
*/
void calculate();
}
/**
* AMD的CPU实现
*/
public class AMDCPU implements CPUApi {
/**
* CPU的针脚数目
*/
private int pins;
/**
* 构造方法,传入CPU的针脚数目
*
* @param pins CPU的针脚数目
*/
public AMDCPU(int pins) {
this.pins = pins;
}
public void calculate() {
System.out.println("now in AMD CPU,pins=" + pins);
}
}
/**
* Intel的CPU实现
*/
public class IntelCPU implements CPUApi {
/**
* CPU的针脚数目
*/
private int pins;
/**
* 构造方法,传入CPU的针脚数目
*
* @param pins CPU的针脚数目
*/
public IntelCPU(int pins) {
this.pins = pins;
}
public void calculate() {
System.out.println("now in Intel CPU,pins=" + pins);
}
}
【CPU工厂】
/**
* 创建CPU的简单工厂
*/
public class CPUFactory {
/**
* 创建CPU接口对象的方法
*
* @param type 选择CPU类型的参数
* @return CPU接口对象的方法
*/
public static CPUApi createCPUApi(CPUType type) {
//根据参数来选择并创建相应的CPU对象
return switch (type) {
case INTEL -> new IntelCPU(1156);
case AMD -> new AMDCPU(939);
};
}
}
【主板定义】
/**
* 主板品牌类型
*/
public enum MainboardType {
GIGABYTE, MSI
}
/**
* 主板的接口
*/
public interface MainboardApi {
/**
* 示意方法,主板都具有安装CPU的功能
*/
void installCPU();
}
/**
* 技嘉的主板
*/
public class GAMainboard implements MainboardApi {
/**
* CPU插槽的孔数
*/
private int cpuHoles;
/**
* 构造方法,传入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;
/**
* 构造方法,传入CPU插槽的孔数
*
* @param cpuHoles CPU插槽的孔数
*/
public MSIMainboard(int cpuHoles) {
this.cpuHoles = cpuHoles;
}
public void installCPU() {
System.out.println("now in MSIMainboard,cpuHoles=" + cpuHoles);
}
}
【主板工厂】
/**
* 创建主板的简单工厂
*/
public class MainboardFactory {
/**
* 创建主板接口对象的方法
*
* @param type 选择主板类型的参数
* @return 主板接口对象的方法
*/
public static MainboardApi createMainboardApi(MainboardType type) {
return switch (type) {
case GIGABYTE -> new GAMainboard(1156);
case MSI -> new MSIMainboard(939);
};
}
}
【装机工程师类】
/**
* 装机工程师的类
*/
public class ComputerEngineer {
/**
* 定义组装机器需要的CPU
*/
private CPUApi cpu = null;
/**
* 定义组装机器需要的主板
*/
private MainboardApi mainboard = null;
/**
* 装机过程
*
* @param cpuType 客户选择所需CPU的类型
* @param mainboardType 客户选择所需主板的类型
*/
public void makeComputer(CPUType cpuType, int mainboardType) {
//1:首先准备好装机所需要的配件
prepareHardwares(cpuType, mainboardType);
//2:组装机器
//3:测试机器
//4:交付客户
}
/**
* 准备装机所需要的配件
*
* @param cpuType 客户选择所需CPU的类型
* @param mainboardType 客户选择所需主板的类型
*/
private void prepareHardwares(CPUType cpuType, int mainboardType) {
//这里要去准备CPU和主板的具体实现,为了示例简单,这里只准备这两个
//可是,装机工程师并不知道如何去创建,怎么办呢?
//直接找相应的工厂获取
this.cpu = CPUFactory.createCPUApi(cpuType);
this.mainboard = MainboardFactory.createMainboardApi(mainboardType);
//测试一下配件是否好用
this.cpu.calculate();
this.mainboard.installCPU();
}
}
【客户购买电脑类】
public class ClientBuyComputer {
public static void main(String[] args) {
//创建装机工程师对象
ComputerEngineer engineer = new ComputerEngineer();
//告诉装机工程师自己选择的配件,让装机工程师组装电脑
engineer.makeComputer(1, 2);
}
}
有何问题
看了上面的实现,会感觉到很简单,通过使用简单工厂来获取需要的CPU和主板对象,然后就可以组装电脑了。有何问题呢?
上面的实现,虽然通过简单工厂解决了:对于装机工程师,只知CPU和主板的接口,而不知道具体实现的问题。但还有一个问题没有解决,什么问题呢?那就是这些CPU对象和主板对象其实是有关系的,是需要相互匹配的。而在上面的实现中,并没有维护这种关联关系,CPU和主板是由客户随意选择的。这是有问题的。
比如在上面实现中的客户端,在调用makeComputer时,传入参数为(1, 2),试试看,运行结果就会如下: now in Intel CPU,pins=1156 now in MSIMainboard, cpuHoles=939 观察上面的结果,就会看出问题。客户选择的CPU的针脚是1156针的,而选择的主板上的CPU插孔却只有939针,根本无法组装。这就是没有维护配件之间的关系造成的。 该怎么解决这个问题呢?
解决方案
用来解决上述问题的一个合理的解决方案就是抽象工厂模式。那么什么是抽象工厂模式呢?
抽象工厂模式的定义
提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们的具体类。
仔细分析上面的问题,其实有两个问题点,一个是只知道所需要的一系列对象的接口,而不知具体实现,或者是不知道具体使用哪一个实现;另外一个是这一系列对象是相关或者相互依赖的,也就是说既要创建接口的对象,还要约束它们之间的关系。
在这个模式里面,会定义一个抽象工厂,在里面虚拟地创建客户端需要的这一系列对象,所谓虚拟的就是定义创建这些对象的抽象方法,并不去真正地实现,然后由具体的抽象工厂的子类来提供这一系列对象的创建。这样一来可以为同一个抽象工厂提供很多不同的实现,那么创建的这一系列对象也就不一样了,也就是说,抽象工厂在这里起到一个约束的作用,并提供所有子类的一个统一外观,来让客户端使用。
抽象工厂模式的结构
- AbstractFactory 抽象工厂,定义创建一系列产品对象的操作接口;
- ConcreteFactory 具体的工厂,实现抽象工厂定义的方法,具体实现一系列产品对象的创建;
- AbstractProduct 定义一类产品对象的接口;
- ConcreteProduct 具体的产品实现对象,通常在具体工厂里面,会选择具体的产品实现对象,来创建符合抽象工厂定义的方法返回的产品类型的对象;
- Client 客户端:主要使用抽象工厂来获取一系列所需要的产品对象,然后面向这些产品对象的接口编程,以实现需要的功能。
抽象工厂模式的实现
/**
* 抽象工厂的接口,声明创建抽象产品对象的操作
*/
public interface AbstractFactory {
/**
* 示例方法,创建抽象产品A的对象
* @return 抽象产品A的对象
*/
public AbstractProductA createProductA();
/**
* 示例方法,创建抽象产品B的对象
* @return 抽象产品B的对象
*/
public AbstractProductB createProductB();
}
/**
* 抽象产品A的接口
*/
public interface AbstractProductA {
//定义抽象产品A相关的操作
}
/**
* 抽象产品B的接口
*/
public interface AbstractProductB {
//定义抽象产品B相关的操作
}
/**
* 具体的工厂实现对象,实现创建具体的产品对象的操作
*/
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();
}
}
/**
* 具体的工厂实现对象,实现创建具体的产品对象的操作
*/
public class ConcreteFactory2 implements AbstractFactory {
public AbstractProductA createProductA() {
return new ProductA2();
}
public AbstractProductB createProductB() {
return new ProductB2();
}
}
/**
* 产品A的具体实现
*/
public class ProductA1 implements AbstractProductA {
//实现产品A的接口中定义的操作
}
/**
* 产品A的具体实现
*/
public class ProductA2 implements AbstractProductA {
//实现产品A的接口中定义的操作
}
/**
* 产品B的具体实现
*/
public class ProductB1 implements AbstractProductB {
//实现产品B的接口中定义的操作
}
/**
* 产品B的具体实现
*/
public class ProductB2 implements AbstractProductB {
//实现产品B的接口中定义的操作
}
/**
* 客户端
*/
public class Client {
public static void main(String[] args) {
//创建抽象工厂对象
AbstractFactory af = new ConcreteFactory1();
//通过抽象工厂来获取一系列的对象,如产品A和产品B
af.createProductA();
af.createProductB();
}
}
抽象工厂重写示例
示例结构
示例代码
/**
* 抽象工厂的接口,声明创建抽象产品对象的操作
*/
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);
}
}
/**
* CPU的接口
*/
public interface CPUApi {
/**
* 示意方法,CPU具有运算的功能
*/
public void calculate();
}
/**
* 主板的接口
*/
public interface MainboardApi {
/**
* 示意方法,主板都具有安装CPU的功能
*/
public void installCPU();
}
/**
*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实现
*/
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);
}
}
/**
* 微星的主板
*/
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);
}
}
/**
* 技嘉的主板
*/
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 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();
}
}
客户端调用
public class Client {
public static void main(String[] args) {
//创建装机工程师对象
ComputerEngineer engineer = new ComputerEngineer();
//客户选择并创建需要使用的装机方案对象
AbstractFactory schema = new Schema1();
//告诉装机工程师自己选择的装机方案,让装机工程师组装电脑
engineer.makeComputer(schema);
}
}
模式讲解
抽象工厂模式的功能
抽象工厂的功能是为一系列相关对象或相互依赖的对象创建一个接口。一定要注意,这个接口内的方法不是任意堆砌的,而是一系列相关或相互依赖的方法,比如上面例子中的CPU和主板,都是为了组装一台电脑的相关对象。 从某种意义上看,抽象工厂其实是一个产品系列,或者是产品簇。上面例子中的抽象工厂就可以看成是电脑簇,每个不同的装机方案,代表一种具体的电脑系列。
实现成接口
AbstractFactory在Java中通常实现成为接口,大家不要被名称误导了,以为是实现成为抽象类。当然,如果需要为这个产品簇提供公共的功能,也不是不可以把AbstractFactory实现成为抽象类,但一般不这么做。
使用工厂方法
AbstractFactory定义了创建产品所需要的接口,具体的实现是在实现类里面,通常在实现类里面就需要选择多种更具体的实现。所以AbstractFactory定义的创建产品的方法可以看成是工厂方法,而这些工厂方法的具体实现就延迟到了具体的工厂里面。也就是说使用工厂方法来实现抽象工厂。
切换产品簇
由于抽象工厂定义的一系列对象通常是相关或者相互依赖的,这些产品对象就构成了一个产品簇,也就是抽象工厂定义了一个产品簇。 这就带来非常大的灵活性,切换一个产品簇的时候,只要提供不同的抽象工厂实现就可以了,也就是说现在是以产品簇作为一个整体被切换。