Abstract Factory(抽象工厂)

266 阅读6分钟

Abstract Factory(抽象工厂)属于创建型模式,工厂类模式抽象程度从低到高分为:简单工厂模式 -> 工厂模式 -> 抽象工厂模式。

意图

提供一个接口以创建一系列相关或相互依赖的对象,而无须指定它们具体的类。

举例子

汽车工厂

我们都知道汽车有很多零部件,随着工业革命带来的分工,很多零件都可以被轻松替换。但实际生活中我们消费者不愿意这样,我们希望买来的宝马车所包含的零部件都是同一系列的,以保证最大的匹配度,从而带来更好的性能与舒适度。

所以消费者不愿意到轮胎工厂、方向盘工厂、车窗工厂去一个个采购,而是将需求提给了宝马工厂这家抽象工厂,由这家工厂负责组装。那你是这家工厂的老板,已知汽车的组成部件是固定的,只是不同配件有不同的型号,分别来自不同的制造厂商,你需要推出几款不同组合的车型来满足不同价位的消费者,你会怎么设计?

迷宫游戏

你做一款迷宫游戏,已知元素有房间、门、墙,他们之间的组合关系是固定的,你通过一套算法生成随机迷宫,这套算法调用房间、门、墙的工厂生成对应的实例。但随着新资料片的放出,你需要生成具有新功能的房间(可以回复体力)、新功能的门(需要魔法钥匙才能打开)、新功能的墙(可以被炸弹破坏),但修改已有的迷宫生成算法违背了开闭原则(需要在已有对象进行修改),如果你希望生成迷宫的算法完全不感知新材料的存在,你会怎么设计?

事件联动

假设我们做一个前端搭建引擎,现在希望做一套关联机制,以实现点击表格组件单元格,可以弹出一个模态框,内部展示一个折线图。已知业务方存在定制表格组件、模态框组件、折线图组件的需求,但组件之间联动关系是确定的,你会怎么设计?

意图解释

在汽车工厂的例子中,我们已知车子的构成部件,为了组装成一辆车子,需要以一定方式拼装部件,而具体用什么部件是需要可拓展的

在迷宫游戏的例子中,我们已知迷宫的组成部分是房间、门、墙,为了生成一个迷宫,需要以某种算法生成许多房间、门、墙的实例,而具体用哪种房间、哪种门、哪种墙是这个算法不关心的,是需要可被拓展的

在事件联动的例子中,我们已知这个表格弹出趋势图的交互场景基本组成元素是表格组件、模态框组件、折线图组件,需要以某种联动机制让这三者间产生联动关系,而具体是什么表格、什么模态框组件、什么折线图组件是这个事件联动所不关心的,是需要可以被拓展的,表格可以被替换为任意业务方注册的表格,只要满足点击 onClick 机制就可以。

意图:提供一个接口以创建一系列相关或相互依赖的对象,而无须指定它们具体的类。

这三个例子不正是符合上面的意图吗?我们要设计的抽象工厂就是要 创建一系列相关或相互依赖的对象,在上面的例子中分别是汽车的组成配件、迷宫游戏的素材、事件联动的组件。而无须指定它们具体的类,也就说明了我们不关心车子方向盘用的是什么牌子,迷宫的房间是不是普通房间,联动机制的折线图是不是用 Echarts 画的,我们只要描述好他们之间的关系即可,这带来的好处是,未来我们拓展新的方向盘、新的房间、新的折线图时,不需要修改抽象工厂。

代码例子

class AbstractFactory {
  createProducts(concreteFactory: ConcreteFactory) {
    const productA = concreteFactory.createProductA();
    const productB = concreteFactory.createProductB();

    // 建立 A 与 B 固定的关联,即便 A 与 B 实现换成任意实现都不受影响
    productA.bind(productB);
  }
}

productA.bind(productB) 是一种抽象表示:

  • 对于汽车工厂的例子,表示组装汽车的过程。
  • 对于迷宫游戏的例子,表示生成迷宫的过程。
  • 对于事件联动的例子,表示创建组件间关联的过程。 假设我们的迷宫有两套素材,分别是普通素材与魔法素材,只要在分别创建普通素材工厂 ConcreteFactoryA,与魔法素材工厂 ConcreteFactoryB,调用 createProducts 时传入的是普通素材,则产出的就是普通素材搭建的迷宫,传入的是魔法素材,则产出的就是用魔法素材搭建的迷宫。

当我们要创建一套新迷宫材料,比如熔岩迷宫,我们只要创建一套熔岩素材(熔岩房间、熔岩门、熔岩墙壁),再组装一个 ConcreteFactoryC 熔岩素材生成工厂传递给 AbstractFactory.createProducts 即可。

我们可以发现,使用抽象工厂模式,我们可以轻松拓展新的素材,比如拓展一套新的汽车配件,拓展一套新的迷宫素材,拓展一套新的事件联动组件,这个过程只需要新建类即可,不需要修改任何类,符合开闭原则

弊端

任何设计模式都有其适用场景,反过来也说明了在某些场景下不适用。

还是上面的例子,如果我们的需求不是拓展一个新轮子、新墙壁、新折线图,而是:

  • 汽车工厂要给汽车加一个新部件:自动驾驶系统。
  • 迷宫游戏要新增一个功能素材:陷阱。
  • 事件联动要新增一个联动对象:明细趋势统计表格。

你看,这种情况不是为已有元素新增一套实现,而是实现一些新元素,就会非常复杂,因为我们不仅要为所有 ConcreteFactory 新增每一个元素,还要修改抽象工厂,以将新元素与旧元素间建立联系,违背了开闭原则。

因此,对于已有元素固定的系统,适合使用抽象工厂,反之不然。