概念

工厂模式(Factory Pattern)是一种创建型设计模式,它提供了一种创建对象的接口,但是允许子类决定实例化哪个类


工厂模式通过定义一个创建对象的接口,但将实际的实例化过程推迟到子类中去完成,从而使得一个类在创建对象时不需要关心具体的实现类

在工厂模式中,通常存在一个工厂类(Factory),它负责创建对象的实例

工厂类可以是简单工厂,也可以是工厂方法或抽象工厂的具体实现


工厂模式的核心思想是将对象的创建过程封装起来,使得客户端代码与具体的对象类解耦,从而提高系统的灵活性和可维护性

这种模式的优点在于,可以在不修改现有代码的情况下很容易地添加新的产品类,而且客户端代码与具体产品类解耦,使得系统更加灵活、可维护和可扩展


举个简单的例子,考虑一个文档编辑器

可以有不同类型的文档(如文本文档、图像文档等),而每种类型的文档可能需要不同的创建方式(如文本文档可以直接创建,而图像文档可能需要先加载图片)

使用工厂模式,可以将文档的创建过程封装到工厂类中,根据不同的类型来创建不同的文档对象,从而实现了文档的统一创建管理


实现条件

工厂模式适用于一些特定的情况,包括但不限于:

  1. 当一个系统需要独立于其产品的创建、组成和表示时,工厂模式能够帮助我们实现这一点
  2. 当要实例化的类在运行时才能确定时,例如,通过动态加载,工厂模式非常有用
  3. 当我们想要避免创建一个与产品类层次平行的工厂类层次时,工厂模式可以帮助我们减少类的数量
  4. 当一个类的实例只有几个不同状态组合中的一种时,使用工厂模式可能会比手动实例化更加方便,因为我们可以创建多个原型并根据需要克隆它们


优点

  1. 封装性

    工厂模式可以隐藏创建对象的复杂逻辑,用户只需要知道他们可以从工厂获取对象,而无需关心对象的创建细节

  2. 扩展性

    如果需要引入新的产品类,只需要扩展工厂类并在其中添加新的创建方法,这样不会影响已有的类代码

  3. 解耦

    客户端代码和具体产品类解耦,客户端只依赖于产品的抽象类

    这意味着客户端代码不需要更改就可以使用不同的产品

  4. 复用性

    工厂类中的代码可以被多次复用,因为它们不依赖于具体的产品类


缺点

  1. 类的数量增多

    每增加一个新的产品类型,都需要增加一个具体的产品类和一个与之对应的具体工厂类,这会使系统中类的总数增加,增加了系统的复杂度

  2. 系统的复杂性

    随着产品变种的增加,相应的工厂类也会增加,系统的复杂性也随之增加,从而增加了理解和维护的难度

  3. 抽象性的问题

    工厂模式的使用需要客户端依赖于工厂类的抽象编程,这对于一些需要理解具体类之间关系的场景可能不是很方便


实现方式

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
/**
* 表示一个人的类
* @class Person
* @param {string} name - 人的名字
* @param {number} age - 人的年龄
*/
class Person {
constructor(name, age) {
this.name = name
this.age = age
}

/**
* 向他人打招呼并介绍自己
*/
greet() {
console.log("Hello, my name is " + this.name + ", and I'm " + this.age + " years old.")
}
}

/**
* 通用工厂模式类,用于创建Person实例
* @class GenericFactoryPattern
*/
class GenericFactoryPattern {

/**
* 创建一个Person实例
* @static
* @param {string} name - 人的名字
* @param {number} age - 人的年龄
* @returns {Person} 返回一个初始化后的Person实例
* @throws {Error} 如果名字不是字符串或年龄不是正数,则抛出错误
*/
static createPerson(name, age) {

// 验证输入参数的合法性
if (typeof name !== 'string') {
throw new Error('Invalid input: "name" parameter must be a string.')
}
if (typeof age !== 'number' || isNaN(age) || age <= 0) {
throw new Error('Invalid input: "age" parameter must be a positive number.')
}

// 创建并返回Person实例
return new Person(name, age)
}
}


export default GenericFactoryPattern


// region HOW TO USE
// 使用工厂类创建对象
let person1 = GenericFactoryPattern.createPerson("Alice", 30)
let person2 = GenericFactoryPattern.createPerson("Bob", 25)

// 调用对象的方法
person1.greet() // 输出:Hello, my name is Alice, and I'm 30 years old.
person2.greet() // 输出:Hello, my name is Bob, and I'm 25 years old.
// endregion HOW TO USE

这段代码定义了一个 Person 类和一个通用的工厂模式类 GenericFactoryPattern,用于创建 Person 实例。下面是对这段代码的解释:

  1. Person 类:
    • 这个类表示一个人,具有两个属性 nameage,分别代表人的名字和年龄
    • 类中有一个方法 greet(),用于向他人打招呼并介绍自己,它会在控制台输出一条打招呼的消息
  2. GenericFactoryPattern 类:
    • 这个类是一个通用的工厂模式类,用于创建 Person 实例
    • 类中有一个静态方法 createPerson(name, age),用于创建一个 Person 实例。该方法接收两个参数,分别是人的名字和年龄
    • 方法内部对输入参数进行合法性验证,确保 name 是字符串且 age 是正数。如果参数不符合要求,则抛出相应的错误
    • 如果参数验证通过,则使用输入参数创建并返回一个初始化后的 Person 实例


场景

  1. JQuery的$()
  2. Vue异步组件
  3. Moment.js 中的 moment()


源代码