精读《设计模式 - Abstract Factory 抽象工厂》

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

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

举例子

若是看不懂上面的意图介绍,没有关系,设计模式须要在平常工做里用起来,结合例子能够加深你的理解,下面我准备了三个例子,让你体会什么场景下会用到这种设计模式。java

汽车工厂

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

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

迷宫游戏

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

事件联动

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

意图解释

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

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

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

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

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

结构图

AbstractFactory 就是咱们要的抽象工厂,描述了建立产品的抽象关系,好比描述迷宫如何生成,表格和趋势图怎么联动。

至于具体用什么方向盘、用什么房间,是由 ConcreteFactory 实现的,因此咱们可能有多个 ConcreteFactory,好比 ConcreteFactory1 实例化的墙壁是普通墙壁,ConcreteFactory2 实例化的墙壁是魔法墙壁,但其对 AbstractFactory 的接口是一致的,因此 AbstractFactory 不须要关心具体调用的是哪个工厂。

AbstractProduct 是产品抽象类,描述了好比方向盘、墙壁、折线图的建立方法,而 ConcreteProduct 是具体实现产品的方法,好比 ConcreteProduct1 建立的表格是用 canvas 画的,折线图是用 G2 画的,而 ConcreteProduct2 建立的表格是用 div 画的,折线图是用 Echarts 画的。

这样,当咱们要拓展一个用 Rcharts 画的折线图,用 svg 画的表格,用 div 画的模态框组成的事件机制时,只须要再建立一个 ConcreteFactory3 作相应的实现便可,再将这个 ConcreteFactory3 传递给 AbstractFactory,并不须要修改 AbstractFactory 方法自己。

代码例子

下面例子使用 javascript 编写。

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 新增每个元素,还要修改抽象工厂,以将新元素与旧元素间创建联系,违背了开闭原则。

所以,对于已有元素固定的系统,适合使用抽象工厂,反之否则。

总结

抽象工厂对新增已有产品的实现适用,对新增一个产品种类不适用,能够参考结合了例子的下图加深理解:

拓展一个熔岩素材包是 增长一种产品风格,适合使用抽象工厂设计模式;拓展一个陷阱是 增长一个产品种类,不适合使用抽象工厂设计模式。为何呢?看下图:

建立迷宫这个抽象工厂作的事情,是把已有的房间、门、墙壁创建关联,由于操做的是抽象类,因此拓展一套具体实现(熔岩素材包)对这个抽象工厂没有感知,这样作很容易。

但若是新增一个产品种类 - 陷阱,能够看到,抽象工厂必须将陷阱与前三者从新创建关联,这就要修改抽象工厂,不符合开闭原则。同时,若是咱们已有素材包 1 ~素材包 999,就须要同时增长 999 个对应的陷阱实现(普通陷阱、魔法陷阱、熔岩陷阱),其工做量会很是大。

所以,只有产品种类稳定时,须要频繁拓展产品风格时才适合用抽象工厂设计模式。

讨论地址是: 精读《设计模式 - Abstract Factory 抽象工厂》· Issue #271 · dt-fe/weekly

若是你想参与讨论,请 点击这里,每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。

关注 前端精读微信公众号

版权声明:自由转载-非商用-非衍生-保持署名( 创意共享 3.0 许可证
相关文章
相关标签/搜索