抽象工厂模式用来解决产品族的实例化问题。比如说现在有个家居设计软件,通过软件模拟房间,摆放各种虚拟的家具,看效果如何。我们可以放入电视柜、茶几、餐桌、床等等。这一系列的家具就叫做产品族。产品族面临的问题是,当一个产品族切换到另外一个产品族时,如何让代码的修改最小。也就是说如何做到开闭原则。
想把设计好的方案从简约现代切换到欧式风格家具,怎么才能做到修改最小?如果采用简单工厂,那么每种产品都对应一个工厂,工厂负责产出不同风格的产品。设计方案中用到 n 种产品就要修改 n 处代码。这显然不是最佳的方法。此时,我们需要抽象工厂模式来解决这个问题。抽象工厂模式中,每个工厂的实现负责生产自己产品族的产品。示意图如下:
抽象工厂模式 - 图1

1. 实现抽象工厂

为了便于理解和展示,我们假设只有两种家具——椅子和桌子。
首先定义每种家具的接口,只有一个方法用来获取家具说明。
椅子:

  1. public interface Chair {
  2. void getChairIntroduction();
  3. }

桌子:

  1. public interface Desk {
  2. void getDeskIntroduction();
  3. }

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

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

欧式风格椅子:

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

桌子也有两种实现,代码这里省略。
产品我们已经编写完成。接下来我们来看看工厂的代码。
首先我们定义一个家具工厂接口,可以生产椅子和桌子:

  1. public interface FurnitureFactory {
  2. Chair createChair();
  3. Desk createDesk();
  4. }

由于我们支持两种不同的风格,所以我们编写两个实现类。
简约风格家具工厂:

  1. public class ModernFurnitureFactory implements FurnitureFactory{
  2. @Override
  3. public Chair createChair() {
  4. return new ModernStyleChair();
  5. }
  6. @Override
  7. public Desk createDesk() {
  8. return new ModernStyleDesk();
  9. }
  10. }

欧式风格家具工厂:

  1. public class EuropeanFurnitureFactory implements FurnitureFactory{
  2. @Override
  3. public Chair createChair() {
  4. return new EuropeanStyleChair();
  5. }
  6. @Override
  7. public Desk createDesk() {
  8. return new EuropeanStyleDesk();
  9. }
  10. }

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

  1. public class Client {
  2. public static void main(String[] args) {
  3. FurnitureFactory furnitureFactory = new EuropeanFurnitureFactory();
  4. Chair chair = furnitureFactory.createChair();
  5. Desk desk = furnitureFactory.createDesk();
  6. chair.getChairIntroduction();
  7. desk.getDeskIntroduction();
  8. }
  9. }

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

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

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

  1. FurnitureFactory furnitureFactory = new ModernFurnitureFactory();

仅通过更换抽象工厂的实现即可实现。修改后执行结果如下:
这是一个现代简约风格的椅子
这是一个现代简约风格的桌子
可以看到已经切换到简约风格的产品族。这个过程中并不需要改任何产品使用的代码。
如果增加别的风格产品族,只需要新建新风格的产品族产品,增加新风格产品族的工厂实现即可。类图:
抽象工厂模式 - 图2

2. 抽象工厂优缺点

2.1 优点

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

    2.2 缺点

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

    3. 抽象工厂适用场景

  4. 你的系统中,需要使用不同产品族中的某一个产品族来操作。 比如说DB源。如果想切换DB,只需要切换DB源即可,其他代码基本上不需要改动;

  5. 你的系统中,需要保证某些产品的一致性。 比如操作系统的外观,当切换到夜间模式时,所有的组件都会换为夜间模式风格。

    4. 小结

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