> For the complete documentation index, see [llms.txt](https://anjia1.gitbook.io/design/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://anjia1.gitbook.io/design/patterns/creational/factory.md).

# 工厂模式

每次使用 `new` 操作符就是在针对实现编程，因为 `new` 意味着在实例化一个具体的类（`new` \~ 具体）。实例化不应该总是公开进行，因为经常会导致耦合问题。工厂模式封装了对象的创建，让客户从具体类中解耦，从而降低了对具体类实现的依赖，进而得到了更有弹性的设计。

判断要不要使用工厂模式，最本质的参考标准有：

* 封装变化：创建逻辑有可能变化，封装成工厂类之后，创建逻辑的变更对调用者透明
* 代码复用：创建代码抽离到独立的工厂类之后可以被复用
* 隔离复杂性：封装复杂的创建逻辑，调用者无需了解如何创建对象
* 控制复杂度：将创建代码抽离出来，让原本的函数或类职责更单一，代码更简洁

在实际的项目中，简单工厂和工厂方法比较常用，抽象工厂相对不常用（应用场景比较特殊）。

* 简单工厂：由一个“对象”来处理具体类的实例化
* 工厂方法：由一个“子类集合”来处理具体类的实例化（类有一种分类方式）
* 抽象工厂：用一个接口来创建一组产品（类有多种分类方式）

## 1. 简单工厂

简单工厂其实不是一个“真正的”设计模式，它更多的是一种编程习惯，但经常被使用。

### 1.1 原理

* 为了让代码的逻辑更清晰、可读性更好，要善于将功能独立的代码块封装成函数
* 为了让类的职责更加单一、代码更加清晰，还可以进一步将函数剥离到一个独立的类中，让这个类只负责对象的创建（这个类就是简单工厂模式类）

通常情况，工厂类的命名都是 `XxxFactory`（但也不是必须的），工厂类中创建对象的方法一般都是 `createXxx`，但也有 `getInstance`, `createInstance`, `newInstance` 甚至是 `valueOf()` 等。

![](/files/nrxuu6qjLsKe75RJV4pp)

```javascript
class PizzaStore {
    constructor(simplePizzaFactory) {
        // 获取参数，设置简单工厂 【~~组合~~】
        this.factory = simplePizzaFactory
    }

    orderPizza(type) {
        // 用简单工厂，创建具体类
        const pizza = this.factory.createPizza(type)
        // 以下代码：使用具体对象
        pizza.prepare()
        pizza.bake()
        pizza.cut()
        pizza.box()
        return pizza
    }
}

class SimplePizzaFactory {
    createPizza(type) {
        let pizza = null
        if (type === 'cheese') {
            pizza = new CheesePizza()
        } else if (type === 'veggie') {
            pizza = new VeggiePizza()
        } else if (type === 'clam') {
            pizza = new ClamPizza()
        } else if (type === 'pepperoni') {
            pizza = new PepperoniPizza()
        }
        return pizza
    }
}
```

```javascript
// 使用
const simplePizzaFactory = new SimplePizzaFactory()
const pizzaStore = new PizzaStore(simplePizzaFactory)
pizzaStore.orderPizza('cheese')
```

一个常用的技巧是将简单工厂定义为静态方法，因此简单工厂模式也叫静态工厂方法模式。

### 1.2 更进一步

尽管在简单工厂模式的代码实现中，有多处 `if` 分支判断逻辑，违背开闭原则，但权衡扩展性和可读性，这样的代码实现在大多数情况下是没有问题的（比如不需要频繁地添加，也没有太多具体类）。虽然应用多态或设计模式提高了代码的扩展性，更加符合开闭原则，但也增加了类的个数，牺牲了代码的可读性。

如果非得要将 `if` 分支逻辑去掉，比较经典的处理办法就是利用多态。实际上这就是工厂方法模式的典型代码实现，我们为（简单）工厂类再创建一个简单工厂（即工厂的工厂）来创建工厂类对象。工厂方法模式比起简单工厂模式更加符合开闭原则。

* 简单工厂：由一个“对象”来处理具体类的实例化
* 工厂方法：由一个“子类集合”来处理具体类的实例化

```javascript
// 
class SimplePizzaFactory {
    createPizza(style, type) {
        let pizza = null

        // 产品新增了 style 分类
        if (style === 'NY') {
            if (type === 'cheese') {
                pizza = new NYStyleCheesePizza()
            } else if (type === 'veggie') {
                pizza = new NYStyleVeggiePizza()
            } else if (type === 'clam') {
                pizza = new NYStyleClamPizza()
            } else if (type === 'pepperoni') {
                pizza = new NYStylePepperoniPizza()
            }

        } else if (style === 'Chicago') {
            if (type === 'cheese') {
                pizza = new ChicagoStyleCheesePizza()
            } else if (type === 'veggie') {
                pizza = new ChicagoStyleVeggiePizza()
            } else if (type === 'clam') {
                pizza = new ChicagoStyleClamPizza()
            } else if (type === 'pepperoni') {
                pizza = new ChicagoStylePepperoniPizza()
            }
        }

        return pizza
    }
}
// 思考：用工厂方法来优化上面的代码
```

## 2. 工厂方法

### 2.1 原理

*工厂方法模式定义了一个创建对象的接口，但由子类决定要实例化哪个类。工厂方法让类把实例化推迟到了子类。*

![](/files/wQlxQFo6f55ErpF7lq0M)![](/files/ChQXVtkTBW4ejcY8v499)

通过每一个工厂，工厂方法模式让我们有办法封装具体类型的实例化。在抽象的工厂父类中，除了 `factoryMethod` 之外的所有方法，都是用来操作工厂方法生产的产品的。只有子工厂类，真正实现工厂方法并创建产品。

![](/files/pEQN7DfO4iikmK5KU7pn)

```javascript
// 父类
class PizzaStore {
    // 子类可以免费获得除了工厂方法之外的所有功能
    orderPizza(type) {
        // 调用工厂方法，创建具体类
        const pizza = createPizza(type)

        pizza.prepare()
        pizza.bake()
        pizza.cut()
        pizza.box()

        return pizza
    }

    // 工厂方法，待子类来实现
    createPizza(type)
}
```

<pre class="language-javascript"><code class="lang-javascript"><strong>// 子类
</strong><strong>class NYStylePizzaStore extends PizzaStore {
</strong><strong>    // 实现工厂方法 【~~继承~~】
</strong>    createPizza(type) {
        let pizza = null
        if (type === 'cheese') {
            pizza = new NYStyleCheesePizza()
        } else if (type === 'veggie') {
            pizza = new NYStyleVeggiePizza()
        } else if (type === 'clam') {
            pizza = new NYStyleClamPizza()
        } else if (type === 'pepperoni') {
            pizza = new NYStylePepperoniPizza()
        }
        return pizza
    }
}

class ChicagoStylePizzaStore extends PizzaStore {
    createPizza(type) {
        let pizza = null
        if (type === 'cheese') {
            pizza = new ChicagoStyleCheesePizza()
        } else if (type === 'veggie') {
            pizza = new ChicagoStyleVeggiePizza()
        } else if (type === 'clam') {
            pizza = new ChicagoStyleClamPizza()
        } else if (type === 'pepperoni') {
            pizza = new ChicagoStylePepperoniPizza()
        }
        return pizza
    }
}
</code></pre>

```javascript
// 使用
const nyFactory = new NYPizzaFactory()
nyStore.orderPizza('cheese')  // 它里面会调用工厂方法，以创建具体类
```

当然工厂类和工厂方法也可以不是抽象的，我们可以定义一个缺省的工厂方法，来生产一些具体产品。这样一来，我们总是有办法创建产品的，即使工厂类没有子类。

### 2.2 使用场景

当创建逻辑比较复杂，是一个大工程的时候，就考虑用工厂模式来封装对象的创建过程，将对象的创建和使用相分离。

那么，“创建逻辑比较复杂”是指哪些情况呢？有以下两种：

1. 代码中存在 `if-else` 分支判断（比如规则配置解析）动态地根据不同类型来创建不同的对象，此时就考虑使用工厂模式，将这一大坨 `if-else` 创建对象的代码抽离出来，放到工厂类中。
   * 当每个对象的创建逻辑都比较简单时，推荐使用简单工厂
   * 当每个对象的创建逻辑都比较复杂时，推荐使用工厂方法
2. 尽管不需要根据不同的类型来创建不同的对象，但是单个对象本身的创建过程比较复杂（比如还要组合其它类对象，做各种初始化操作），此时也可以考虑使用工厂模式，将对象的创建过程封装到工厂类中。因为单个对象本身的创建逻辑就比较复杂，所以建议使用工厂方法模式。

除了上面提到的这两种情况外，如果创建对象的逻辑并不复杂，那可以直接通过 `new` 来创建对象，不需要使用工厂模式。

## 3. 抽象工厂

### 3.1 解决的问题

如果类的分类方式不止一种（比如既可以按照文件格式来分类，又可以按照解析对象来分类），此时如果还继续用工厂方法来实现的话，就会产生大量的类。

抽象工厂就是针对这种非常特殊的场景诞生的，我们可以让一个工厂负责创建多个不同类型的对象（IRuleConfigParser、ISystemConfigParser 等），而不是只创建一种 parser 对象，这样就可以有效地减少工厂类的个数。

### 3.2 原理

*抽象工厂模式提供一个接口，来创建相关或依赖对象的家族（创建产品的家族），而不需要指定具体类。*

抽象工厂允许客户使用一个抽象接口来创建一组相关产品，而不需要关心实际生产的具体产品是什么。通过这样的方式，客户就从所有的特定具体产品解耦。

![](/files/ogZPYRFAev9DaQyES5lp)![](/files/x3E26XKyJfKyfyPswF1m)

抽象工厂为创建的产品家族提供了一个抽象，其子类定义如何生产这些产品。要使用抽象工厂，首先要实例化一个工厂，并传入一些针对抽象类型编写的代码。

抽象工厂的方法经常实现为工厂方法。抽象工厂的工作是定义一个接口，用来创建一组产品。该接口的每个方法负责创建一个具体的产品，我们实现抽象工厂的子类就是提供这些实现。因此在抽象工厂中，用工厂方法来实现生产方法是非常自然的方式。

<pre class="language-javascript"><code class="lang-javascript"><strong>// 1. 在（工厂方法的）抽象父类中
</strong><strong>class PizzaStore {
</strong>    orderPizza(type){
        // 第一层工厂方法：调用工厂方法，实例化具体对象
        const pizza = createPizza(type)
        // 第二层抽象工厂：会传入第一层的具体工厂，即以对象组合的方式复用
        pizza.prepare()

        pizza.bake()
        pizza.cut()
        pizza.box()

        return pizza
    }
    createPizza(type)  // 第一层工厂方法，由子类实现
}

// 2. 在（工厂方法的）具体工厂中
class NYPizzaStore extends PizzaStore {
    // 实现第一层的工厂方法【~~继承~~】
    createPizza(type) {
        let pizza = null

        // 第二层：实例化一个（抽象工厂的）具体工厂，其父类是一组接口（产品家族）【~~组合~~】
        const ingredientFacory = new NYIngredientFacory()        
        
        // 第二层：在实例化具体类时，把工厂传参进去
        if (type === 'cheese') {
            pizza = new CheesePizza(ingredientFacory)
            pizza.setName('New York Style Cheese Pizza')
        } else if (type === 'veggie') {
            pizza = new VeggiePizza(ingredientFacory)
            pizza.setName('New York Style Veggie Pizza')
        } else if (type === 'clam') {
            pizza = new ClamPizza(ingredientFacory)
            pizza.setName('New York Style Clam Pizza')
        } else if (type === 'pepperoni') {
            pizza = new PepperoniPizza(ingredientFacory)
            pizza.setName('New York Style Pepperoni Pizza')
        }
        return pizza
    }
}
</code></pre>

```javascript
// 3. 在具体类的代码里
class CheesePizza extends Pizza {

    constructor(ingredientFacory) {
        // 获取参数，设置本地工厂【~~组合~~】
        this.ingredientFacory = ingredientFacory
    }

    // 工厂方法：实现父类中的抽象方法【~~继承~~】
    prepare() {
        // 用组合来的本地工厂，生产具体的原料
        dough = this.ingredientFacory.createDough()
        sauce = this.ingredientFacory.createSauce()
        cheese = this.ingredientFacory.createCheese()
        clam = this.ingredientFacory.createClam()
    }
}
```

```javascript
// 最终的使用：和工厂方法的代码一样
nyStore = new NYPizzaStore()
pizza = nyStore.orderPizza('cheese')
```

思考：所以工厂方法和抽象工厂的最本质的区别，到底工厂方法的接口个数，还是复用的手段（继承 or 组合）？应该是后者，也就是说抽象工厂的核心是“对象组合”。那么从产品需求上来讲，是先确认一个类别（第一层工厂方法是用继承），再确认另一个类别（第二层是组合+工厂方法（里面调一组接口））。

所以，抽象工厂的核心代码，如下：

```javascript
const nyIngredientFacory = new NYIngredientFacory()  // 实例化抽象工厂的具体工厂
const pizza = new CheesePizza(nyIngredientFacory)    // 实例化具体产品

// 在具体产品的内部：接收参数，进行组合，用具体工厂生产
class CheesePizza extends Pizza {
    constructor(ingredientFacory) {
        this.ingredientFacory = ingredientFacory
    }
    prepare() {
        dough = this.ingredientFacory.createDough()
        sauce = this.ingredientFacory.createSauce()
        cheese = this.ingredientFacory.createCheese()
        clam = this.ingredientFacory.createClam()
    }
}

// 这里，抽象工厂是用“原料工厂”区分了不同的 style
// 之前，工厂方法是直接用“子类”区分了不同的 style
```

### 3.3 对比工厂方法

<table><thead><tr><th width="120.33333333333331">工厂模式</th><th width="184">创建对象</th><th>说明</th></tr></thead><tbody><tr><td>工厂方法</td><td>通过类继承</td><td>对象创建被委托给了子类<br>子类实现工厂方法来创建对象</td></tr><tr><td>抽象工厂</td><td>通过对象组合</td><td>对象创建是在工厂接口暴露的方法中</td></tr></tbody></table>

<table><thead><tr><th width="124">工厂模式</th><th width="180">接口</th><th>说明</th></tr></thead><tbody><tr><td>工厂方法</td><td>简单</td><td>只创建一个产品，因此只需要一个方法</td></tr><tr><td>抽象工厂</td><td>大而全</td><td>需创建多个产品（产品家族），因此需要大而全的接口</td></tr><tr><td></td><td>适合接口明确且稳定</td><td>当要扩展相关产品时（比如添加新产品）就要修改接口</td></tr></tbody></table>

<table><thead><tr><th width="124.33333333333331">工厂模式</th><th>意图</th></tr></thead><tbody><tr><td>工厂方法</td><td>允许一个类延迟实例化到其子类（使用子类来创建对象）</td></tr><tr><td>抽象工厂</td><td>创建相关对象家族，不必依赖于其具体类</td></tr></tbody></table>

## 4. 写在最后

模式会让设计更灵活，但未必会更小。

通常情况，设计会从使用工厂方法开始。当设计者发现需要更大的灵活性时，便会向其它创建型模式（抽象工厂、建造者、原型）演化，因为它们三个比工厂方法更灵活，但也更复杂。当在设计标准之间进行权衡的时候，了解多个模式可以有更多的选择余地。
