抽象工厂模式

抽象工厂模式用来解决产品族的实例化问题。比如说现在有个家居设计软件,通过软件模拟房间,摆放各种虚拟的家具,看效果如何。我们可以放入电视柜、茶几、餐桌、床等等。这一系列的家具就叫做产品族。产品族面临的问题是,当一个产品族切换到另外一个产品族时,如何让代码的修改最小。也就是说如何做到开闭原则。

想把设计好的方案从简约现代切换到欧式风格家具,怎么才能做到修改最小?如果采用简单工厂,那么每种产品都对应一个工厂,工厂负责产出不同风格的产品。设计方案中用到 n 种产品就要修改 n 处代码。这显然不是最佳的方法。此时,我们需要抽象工厂模式来解决这个问题。抽象工厂模式中,每个工厂的实现负责生产自己产品族的产品。示意图如下:
图片描述

1. 实现抽象工厂

为了便于理解和展示,我们假设只有两种家具----椅子和桌子。

首先定义每种家具的接口,只有一个方法用来获取家具说明。

椅子:

public interface Chair {
    void getChairIntroduction();
}

桌子:

public interface Desk {
    void getDeskIntroduction();
}

以椅子为例,我们分别实现简约现代和欧式两种风格。
简约现代风格椅子:

public class ModernStyleChair implements Chair {
    @Override
    public void getChairIntroduction() {
        System.out.println("这是一个现代简约风格的椅子");
    }
}

欧式风格椅子:

public class EuropeanStyleChair implements Chair {
    @Override
    public void getChairIntroduction() {
        System.out.println("这是一个欧式风格的椅子");
    }
}

桌子也有两种实现,代码这里省略。

产品我们已经编写完成。接下来我们来看看工厂的代码。

首先我们定义一个家具工厂接口,可以生产椅子和桌子:

public interface FurnitureFactory {
    Chair createChair();
    Desk createDesk();
}

由于我们支持两种不同的风格,所以我们编写两个实现类。

简约风格家具工厂:

public class ModernFurnitureFactory implements FurnitureFactory{
    @Override
    public Chair createChair() {
        return new ModernStyleChair();
    }

    @Override
    public Desk createDesk() {
        return new ModernStyleDesk();
    }
}

欧式风格家具工厂:

public class EuropeanFurnitureFactory implements FurnitureFactory{
    @Override
    public Chair createChair() {
        return new EuropeanStyleChair();
    }

    @Override
    public Desk createDesk() {
        return new EuropeanStyleDesk();
    }
}

上面的代码中,每种工厂各自实现如何生产两种不同的家具。

客户端代码如下:

public class Client {
    public static void main(String[] args) {
        FurnitureFactory furnitureFactory = new EuropeanFurnitureFactory();
        Chair chair = furnitureFactory.createChair();
        Desk desk = furnitureFactory.createDesk();

        chair.getChairIntroduction();
        desk.getDeskIntroduction();
    }
}

客户端代码中,我们实例化的是欧式家具工厂,那么所生产的椅子和桌子应该是欧式风格。执行后输出如下:

这是一个欧式风格的椅子
这是一个欧式风格的桌子

和我们的预期相符。如果想要更换产品族,从现代简约切换到欧式,我们只需要修改一处代码。

FurnitureFactory furnitureFactory = new ModernFurnitureFactory();

仅通过更换抽象工厂的实现即可实现。修改后执行结果如下:

这是一个现代简约风格的椅子
这是一个现代简约风格的桌子

可以看到已经切换到简约风格的产品族。这个过程中并不需要改任何产品使用的代码。

如果增加别的风格产品族,只需要新建新风格的产品族产品,增加新风格产品族的工厂实现即可。
类图:
图片描述

2. 抽象工厂优缺点

2.1 优点

  1. 分离了产品类和客户端类:客户端只依赖抽象的产品接口。此外,如何生产产品被封装在工厂内部;
  2. 方便切换产品族:客户端代码只需要初始化一次工厂实现。这意味着在切换产品族的时候,只需要修改一行代码,换一个工厂实现即可;
  3. 保证产品的一致性:使用抽象工厂,可以保证你从相同工厂生产的产品都属于同一个产品族。不会出现椅子是现代简约风格,而桌子是欧式风格的情况。

2.2 缺点

添加新的产品时,改动较多。例子从两个维度定义产品,一是不同产品,比如桌子、椅子。另外是不同族,例如现代简约和欧式。使用抽象工厂,优化了产品族,也就是第二个维度变化的难度。但是当添加新的产品时改动就会比较多。比如我们要添加一个新的产品是电视柜。那么需要修改抽象工厂,添加生产电视柜的方法。此外,有几种工厂的实现,我们就需要修改几个类,添加具体的生产实现。

3. 抽象工厂适用场景

  1. 你的系统中,需要使用不同产品族中的某一个产品族来操作。 比如说DB源。如果想切换DB,只需要切换DB源即可,其他代码基本上不需要改动;
  2. 你的系统中,需要保证某些产品的一致性。 比如操作系统的外观,当切换到夜间模式时,所有的组件都会换为夜间模式风格。

4. 小结

抽象工厂可以做到一组产品的使用和生产相分离。通过抽象工厂模式,我们切换一组产品族的,只需要更换抽象工厂实现即可。由于产品生产被分离出去,所以添加新的产品族完全通过扩展来实现的。很好的实现了开闭原则。如果你要生产的产品很多,而且是一个产品族。并且面临不同产品族切换的情况。那么可以考虑通过抽象工厂来实现。