抽象工厂模式


# 抽象工厂模式

# 介绍

工厂模式(Factory Pattern),根据输入的不同返回不同类的实例,一般用来创建同一类对象。工厂方式的主要思想是将对象的创建与对象的实现分离

抽象工厂模式(Abstract Factory Pattern),通过对类的工厂抽象使其业务用于对产品类簇的创建,而不是负责创建某一类产品的实例。关键在于使用抽象类制定了实例的结构,调用者直接面向实例的结构编程,从实例的具体实现中解耦

我们知道 JavaScript 并不是强面向对象语言,所以使用传统编译型语言比如 JAVA 等实现的设计模式和 JavaScript 不太一样,比如 JavaScript 中没有原生的类和接口等(不过 ES6+ 渐渐提供类似的语法糖),我们可以用变通的方式来解决。最重要的是设计模式背后的核心思想,和它所要解决的问题。

# 通俗的示例

还是以之前工厂模式中举的 KFC 的例子,之前我们说 KFC 是工厂,汉堡是产品,工厂封装做产品的工作,做好直接给购买者。

现在进行扩展,汉堡属于一种具体的产品,同样还有薯条、咖啡、牛奶等也都是产品。无论你点哪个产品,他们都具有同样的属性:油炸的都可以吃,冲调的都可以喝。对于工厂也一样,KFC 可以做汉堡、薯条、咖啡、牛奶,麦当劳和华莱士也可以,那么这些工厂就具有同样的功能结构。

这样的场景就是属于抽象工厂模式的例子:

  • 食品类属于抽象产品类,制定具体产品类所具备的属性(可以喝、可以吃)。
  • 具体的汉堡类、薯条类属于产品类,在这里面实现了该产品自身的具体属性值(原材料、口味、佐料)。
  • 工厂类和之前的工厂模式一样,负责具体生产产品实例。

访问者通过柜台获取想拿的产品。只要我们点的是冲调产品,即使还没有被做出来,我们就知道是可以喝的。

推广一下,工厂功能也可以被抽象(抽象工厂类),继承这个类的工厂实例都具有油炸食品和冲调饮品的功能,这样也完成了抽象类对实例的结构约束。

在类似场景中,这些例子有这样的特点:只要实现了抽象类的实例,都实现了抽象类制定的结构。

# 抽象工厂模式的通用实现

我们提炼一下抽象工厂模式,快餐店品牌是抽象工厂类(AbstractFactory),实现抽象工厂类的 KFC 是具体的工厂(Factory),它和麦当劳、华莱士等存在共同的功能结构。食品种类是抽象类(AbstractFactory),而实现抽象类的菜品是具体的产品(Product)。

总的来说就是一句话:只要这几个平级的类存在共同的功能结构,就可以将共同结构作为抽象类抽象出来。

通过工厂拿到实现了不同抽象类的产品,这些产品可以根据实现的抽象类被区分为类簇。主要有下面几个概念:

  • Factory:工厂类,负责返回产品实例。
  • AbstractFactory:抽象工厂类,制定工厂实例的结构。
  • Product:产品类,访问者从工厂中拿到的产品实例,实现抽象类。
  • AbstractProduct:产品抽象类,由具体产品实现,制定产品实例的结构。

结构如下:

抽象工厂模式结构图

(抽象工厂模式结构图)

代码如下:

我们知道 JavaScript 并不强面向对象,也没有提供抽象类(至少目前没有提供),但是可以模拟抽象类。用对 new.target 来判断 new 的类,在父类方法中 throw new Error(),如果子类中没有实现这个方法就会抛错,这样来模拟抽象类。

/* 工厂 抽象类 */
class AbstractFactory {
  constructor() {
    if (new.target === AbstractFactory)
      throw new Error('抽象类不能直接实例化!')
  }

  /* 抽象方法 */
  createProduct1() { throw new Error('抽象方法不能调用!') }
}

/* 具体饭店类 */
class Factory extends AbstractFactory {
  constructor() { super() }

  createProduct1(type) {
    switch (type) {
      case 'Product1':
        return new Product1()
      case 'Product2':
        return new Product2()
      default:
        throw new Error('当前没有这个产品 -。-')
    }
  }
}

/* 抽象产品类 */
class AbstractProduct {
  constructor() {
    if (new.target === AbstractProduct)
      throw new Error('抽象类不能直接实例化!')
    this.kind = '抽象产品类1'
  }

  /* 抽象方法 */
  operate() { throw new Error('抽象方法不能调用!') }
}

/* 具体产品类1 */
class Product1 extends AbstractProduct {
  constructor() {
    super()
    this.type = 'Product1'
  }

  operate() { console.log(this.kind + ' - ' + this.type) }
}

/* 具体产品类2 */
class Product2 extends AbstractProduct {
  constructor() {
    super()
    this.type = 'Product2'
  }

  operate() { console.log(this.kind + ' - ' + this.type) }
}


const factory = new Factory()

const prod1 = factory.createProduct1('Product1')
prod1.operate()										// 输出: 抽象产品类1 - Product1
const prod2 = factory.createProduct1('Product3')	// 输出: Error 当前没有这个产品 -。-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65

如果希望增加第二个类簇的产品,除了需要改一下对应工厂类之外,还需要增加一个抽象产品类,并在抽象产品类基础上扩展新的产品。

我们在实际使用的时候不一定需要每个工厂都继承抽象工厂类,比如只有一个工厂的话我们可以直接使用工厂模式,在实战中灵活使用。

# 设计原则验证

  • 不符合开放封闭原则,因为扩展新类簇时需要同时创建新的抽象类。

# 抽象工厂模式的优缺点

优点:

  • 抽象产品类将产品的结构抽象出来,访问者不需要知道产品的具体实现,只需要面向产品的结构编程即可,从产品的具体实现中解耦

缺点:

  • 扩展新类簇的产品类比较困难,因为需要创建新的抽象产品类,并且还要修改工厂类,违反开闭原则。
  • 带来了系统复杂度,增加了新的类,和新的继承关系。

# 抽象工厂模式的适用场景

如果一组实例都有相同的结构,那么就可以使用抽象工厂模式。

# 其他相关模式

# 抽象工厂模式与工厂模式

工厂模式和抽象工厂模式的区别:

  • 工厂模式 主要关注单独的产品实例的创建。
  • 抽象工厂模式 主要关注产品类簇实例的创建,如果产品类簇只有一个产品,那么这时的抽象工厂模式就退化为工厂模式了。

根据场景灵活使用即可。

(完)