抽象工厂模式

什么是抽象工厂模式

抽象工厂模式是一种软件设计模式,它提供了一种方法来创建相关或依赖对象的组合,而不需要指定它们的具体类。

这种模式把对象的创建延迟到子类,使得子类可以决定实例化哪些类。这样,程序可以在运行时动态地改变它所使用的对象,而不需要修改代码。

抽象工厂模式通常由以下几个部分组成:

  • 抽象工厂(Abstract Factory):它是工厂模式的核心,是与应用程序无关的。它声明了用于创建一组相关或相互依赖对象的接口,每个接口方法对应一个产品。

  • 具体工厂(Concrete Factory):它实现了在抽象工厂中声明的创建产品的方法。

  • 抽象产品(Abstract Product):它是工厂模式所创建的对象的父类,封装了所有它的共性。

  • 具体产品(Concrete Product):它是继承自抽象产品的具体类,实现了在抽象产品中声明的抽象方法。

例1

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
interface Animal {
makeSound(): void;
}

class Dog implements Animal {
makeSound() {
console.log('Bark');
}
}

class Cat implements Animal {
makeSound() {
console.log('Meow');
}
}

interface AnimalFactory {
createAnimal(): Animal;
}

class DogFactory implements AnimalFactory {
createAnimal() {
return new Dog();
}
}

class CatFactory implements AnimalFactory {
createAnimal() {
return new Cat();
}
}

// 用法
const dogFactory = new DogFactory();
const dog = dogFactory.createAnimal();
dog.makeSound(); // Bark

const catFactory = new CatFactory();
const cat = catFactory.createAnimal();
cat.makeSound(); // Meow

在这个例子中,我们有 Animal 接口和两个实现它的类 DogCat。我们还有一个 AnimalFactory 接口和两个实现它的类 DogFactoryCatFactory,这些类分别生成 DogCat 对象。

然后我们可以使用工厂类来创建新的动物对象,而无需直接使用具体的类名。这使得我们可以在不改变客户端代码的情况下更改动物类的实现。

例2

假设你正在开发一个用于制作披萨的应用程序。披萨的制作需要许多不同的原料,包括面团、酱料、蔬菜和肉类。你可以使用抽象工厂模式来创建用于制作这些原料的工厂。

例如,你可以创建一个抽象工厂接口 PizzaIngredientFactory,其中定义了用于创建各种原料的方法,如 createDough()createSauce()createVeggies()createMeat()。然后,你可以创建一个具体工厂类,如 NYPizzaIngredientFactoryChicagoPizzaIngredientFactory,它们实现了 PizzaIngredientFactory 接口并具体提供了如何创建原料的方法。

然后,你可以使用这些工厂来创建不同类型的披萨,如芝士披萨或辣香肠披萨。每种披萨都可以使用特定工厂的方法来创建所需的原料,而无需关心它们是如何制作的。

下面是 typescript 代码实例:
首先,你可以创建一个抽象工厂接口,如下所示:

1
2
3
4
5
6
interface PizzaIngredientFactory {
createDough(): Dough;
createSauce(): Sauce;
createVeggies(): Veggies[];
createMeat(): Meat;
}

定义披萨原料接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface Dough {
// ...
}

interface Sauce {
// ...
}

interface Veggies {
// ...
}

interface Meat {
// ...
}

实现披萨原料工厂接口:

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
class NYPizzaIngredientFactory implements PizzaIngredientFactory {
createDough(): Dough {
// 创建纽约风味的面团
}

createSauce(): Sauce {
// 创建纽约风味的酱料
}

createVeggies(): Veggies[] {
// 创建纽约风味的蔬菜
}

createMeat(): Meat {
// 创建纽约风味的肉类
}
}


class ChicagoPizzaIngredientFactory implements PizzaIngredientFactory {
createDough(): Dough {
// 创建芝加哥风味的面团
}

createSauce(): Sauce {
// 创建芝加哥风味的酱料
}

createVeggies(): Veggies[] {
// 创建芝加哥风味的蔬菜
}

createMeat(): Meat {
// 创建芝加哥风味的肉类
}
}

使用披萨原料工厂创建披萨

1
2
3
4
5
6
7
8
9
10
class Pizza {
// ...

prepare(ingredientFactory: PizzaIngredientFactory) {
this.dough = ingredientFactory.createDough();
this.sauce = ingredientFactory.createSauce();
this.veggies = ingredientFactory.createVeggies();
this.meat = ingredientFactory.createMeat();
}
}

然后,你可以创建一个具体工厂类,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class NYPizzaIngredientFactory implements PizzaIngredientFactory {
createDough(): Dough {
// Return a thin crust dough
}
createSauce(): Sauce {
// Return a marinara sauce
}
createVeggies(): Veggies[] {
// Return an array of vegetables
}
createMeat(): Meat {
// Return sausage as the meat
}
}

定义不同风味的 pizza:

1
2
3
4
5
6
7
class CheesePizza extends Pizza {
// ...
}

class PepperoniPizza extends Pizza {
// ...
}

最后,你可以使用这些工厂来创建披萨对象,如下所示:

1
2
3
4
5
6
7
8
9
10
// 创建原料工厂
const nyPizzaIngredientFactory = new NYPizzaIngredientFactory();
const chicagoPizzaIngredientFactory = new ChicagoPizzaIngredientFactory();

// 创建不同的 pizza
const cheesePizza = new CheesePizza();
cheesePizza.prepare(nyPizzaIngredientFactory);

const pepperoniPizza = new PepperoniPizza();
pepperoniPizza.prepare(chicagoPizzaIngredientFactory);

现在,你就可以使用不同的工厂来创建具有不同风味的披萨,而无需关心如何制作这些披萨的原料。

如果你想要添加新的披萨种类或者修改已有的披萨种类,只需要扩展 Pizza 类即可。你也可以通过创建新的工厂类来支持不同风味的披萨,而无需修改现有的代码。

总的来说,抽象工厂模式可以让你更轻松地创建相关对象的组合,并且可以更灵活地更改这些对象的实现方式。它在软件开发中非常常用,可以为你的应用程序提供很大的灵活性和扩展性。

抽象工厂模式和工厂方法模式的关系

抽象工厂模式提供了一种方法来创建一组相关的产品对象,而工厂方法模式提供了一种方法来创建单个产品对象。

在工厂方法模式中,有多个具体的工厂类来生成不同的产品对象。如果这些具体的工厂类之间有相同的部分,那么我们可以将这些相同的部分封装到一个抽象工厂类中,具体的工厂类继承抽象工厂类,实现生成产品对象的方法。

这样就可以将工厂方法模式进一步抽象成抽象工厂模式。在抽象工厂模式中,抽象工厂负责声明创建一组相关的产品对象的方法,具体的工厂负责生成具体的产品对象。

总的来说,抽象工厂模式是在工厂方法模式的基础上进一步抽象的一种设计模式,它提供了一种方法来创建一组相关的产品对象。

练习

题目

你正在开发一个用于制作冰激凌的应用程序。冰激凌的制作需要许多不同的原料,包括牛奶、香料、水果和冰块。你可以使用抽象工厂模式来创建用于制作这些原料的工厂。

请使用 TypeScript 实现以下内容:

  1. 定义冰激凌原料工厂接口 IceCreamIngredientFactory,其中定义了用于创建各种原料的方法,如 createMilk()createSpice()createFruit()createIce()

  2. 定义冰激凌原料接口 MilkSpiceFruitIce

  3. 实现冰激凌原料工厂接口,创建两个具体工厂类:

  • VanillaIceCreamIngredientFactory:创建用于制作香草冰激凌的原料。
  • ChocolateIceCreamIngredientFactory:创建用于制作巧克力冰激凌的原料。
  1. 定义冰激凌类 IceCream,其中包含一个 prepare() 方法,该方法使用冰激凌原料工厂创建所需的所有原料。

  2. 定义两种具体的冰激凌类:

  • VanillaIceCream:制作香草冰激凌。
  • ChocolateIceCream:制作巧克力冰激凌。
  1. 创建冰激凌。

答案

  1. 定义一个抽象工厂函数的接口:

    1
    2
    3
    4
    5
    6
    Interface IceCreamIngredientFactory {
    createMilk(): Milk;
    createSpice(): Spice;
    createFruit(): Fruit;
    createIce(): Ice;
    }
  2. 定义原料接口:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    interface Milk {
    //...
    }

    interface Spice{
    //...
    }

    interface Fruit {
    //...
    }

    interface Ice {
    //...
    }
  3. 实现两个具体原料工厂

  • VanillaIceCreamIngredientFactory:创建用于制作香草冰激凌的原料。
  • ChocolateIceCreamIngredientFactory:创建用于制作巧克力冰激凌的原料。
    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
    class VanillaIceCreamIngredientFactory implements IceCreamIngredientFactory{
    createMilk(): Milk{
    // Vanilla...
    }
    createSpice(): Spice{
    // Vanilla...
    }
    createFruit(): Fruit{
    // Vanilla...
    }
    createIce(): Ice{
    // Vanilla...
    }
    }

    class ChocolateIceCreamIngredientFactory implements IceCreamIngredientFactory{
    createMilk(): Milk{
    // Chocolate...
    }
    createSpice(): Spice{
    // Chocolate...
    }
    createFruit(): Fruit{
    // Chocolate...
    }
    createIce(): Ice{
    // Chocolate...
    }
    }
  1. 定义冰激凌类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class IceCream{
    // ...
    preper(iceCreamIngredientFactory: IceCreamIngredientFactory){
    this.milk = iceCreamIngredientFactory.createMilk();
    this.spice = iceCreamIngredientFactory.createSpice();
    this.fruit = iceCreamIngredientFactory.createFruit();
    this.ice = iceCreamIngredientFactory.createIce();
    }
    }
  2. 定义两个具体的冰激凌类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    class VanillaIceCream extend IceCream {
    //...

    preper(iceCreamIngredientFactory: VanillaIceCreamIngredientFactory){
    this.milk = iceCreamIngredientFactory.createMilk();
    this.spice = iceCreamIngredientFactory.createSpice();
    this.fruit = iceCreamIngredientFactory.createFruit();
    this.ice = iceCreamIngredientFactory.createIce();
    }
    }
    class ChocolateIceCream extend IceCream{
    //...
    preper(iceCreamIngredientFactory: ChocolateIceCreamIngredientFactory){
    this.milk = iceCreamIngredientFactory.createMilk();
    this.spice = iceCreamIngredientFactory.createSpice();
    this.fruit = iceCreamIngredientFactory.createFruit();
    this.ice = iceCreamIngredientFactory.createIce();
    }
    }
  3. 创建原料工厂, 创建冰激凌

    1
    2
    3
    4
    5
    6
    7
    const vanillaIceCreamIngredientFactory = new VanillaIceCreamIngredientFactory();
    const ChocolateIceCreamIngredientFactory = new ChocolateIceCreamIngredientFactory();
    const vanillaIceCream = new VanillaIceCream();
    vanillaIceCream.preper(vanillaIceCreamIngredientFactory)

    const chocolateIceCream = new ChocolateIceCream();
    chocolateIceCream.preper(ChocolateIceCreamIngredientFactory);

抽象工厂模式所遵循的原则

  1. 依赖倒置原则(Dependency Inversion Principle):抽象不应该依赖于细节,细节应该依赖于抽象。在抽象工厂模式中,抽象工厂是依赖于抽象产品的,而具体工厂和具体产品是依赖于抽象工厂和抽象产品的。

  2. 开闭原则(Open-Closed Principle):软件实体应该对扩展开放,对修改关闭。在抽象工厂模式中,抽象工厂和抽象产品是对扩展开放的,因为可以添加新的产品和工厂,但是对修改关闭,因为无需修改已有的产品和工厂。

  3. 里氏替换原则(Liskov Substitution Principle):所有引用基类(父类)的地方必须能透明地使用其子类的对象。在抽象工厂模式中,客户端使用抽象工厂和抽象产品,因此可以使用具体工厂和具体产品的对象,而不会受到影响。

抽象工厂模式的优缺点

抽象工厂模式的优点包括:

  1. 它支持产品的多种实现,使得可以在不更改客户端的情况下更换产品的实现。
  2. 它允许系统独立于产品的创建、组合和表示。
  3. 它提供了一种灵活的方法来创建产品,使得可以在不更改客户端的情况下更改产品的实现。

缺点:

  1. 它增加了系统的复杂度,因为它提供了更多的抽象层。

抽象工厂模式的适用场景

  1. 你需要为多种类型的产品提供一个抽象接口。
  2. 你希望系统独立于产品的创建、组合和表示。
  3. 你希望在不更改客户端的情况下更换产品的实现。
  4. 你希望在系统中使用一组产品,而无需指定这些产品的具体类

更多的例子

  1. 创建一个图形编辑器,可以绘制圆形、矩形和直线。你可以使用抽象工厂模式来创建这些图形的不同实现,例如圆形可以使用圆周率计算面积,而矩形可以使用长宽乘积计算面积。

  2. 抽象工厂模式可以用来创建不同的 UI 元素,比如表格、按钮、输入框等。

假设你正在开发一个用于显示数据的应用,其中有多种不同的表格可供选择,每种表格都有自己的样式和功能。在这种情况下,你可以使用抽象工厂模式来创建不同类型的表格。

下面是一个使用抽象工厂模式创建表格的示例:

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
// 定义抽象工厂
class TableFactory {
createTable(type) {
throw new Error('createTable method must be implemented')
}
}

// 定义具体工厂
class BasicTableFactory extends TableFactory {
createTable(type) {
switch (type) {
case 'basic':
return new BasicTable()
case 'striped':
return new StripedTable()
default:
throw new Error('Invalid table type')
}
}
}

// 定义抽象产品
class Table {
render() {
throw new Error('render method must be implemented')
}
}

// 定义具体产品
class BasicTable extends Table {
render() {
console.log('Rendering basic table...')
}
}

class StripedTable extends Table {
render() {
console.log('Rendering striped table...')
}
}

// 使用工厂创建产品
const factory = new BasicTableFactory()
const table = factory.createTable('striped')
table.render() // 输出: Rendering striped table...

在这个示例中,我们定义了一个抽象工厂 TableFactory 和一个具体工厂 BasicTableFactory,以及一个抽象产品 Table 和两个具体产品 BasicTableStripedTable。然后,我们使用 BasicTableFactory

实例来创建不同类型的表格,最后我们调用表格的 render 方法来渲染表格。

这样,我们就可以使用抽象工厂模式来创建不同类型的表格,而无需关心具体的实现细节。如果需要增加新的表格类型,只需要在 BasicTableFactory 中添加新的代码来创建新的表格对象即可。

  1. 假设你正在开发一个画图应用,其中有多种不同的图形可供选择,每种图形都有自己的样式和功能。在这种情况下,你可以使用抽象工厂模式来创建不同类型的图形。

下面是一个使用抽象工厂模式创建图形的示例:

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
// 定义抽象工厂
class ShapeFactory {
createShape(type) {
throw new Error('createShape method must be implemented')
}
}

// 定义具体工厂
class BasicShapeFactory extends ShapeFactory {
createShape(type) {
switch (type) {
case 'circle':
return new Circle()
case 'rectangle':
return new Rectangle()
default:
throw new Error('Invalid shape type')
}
}
}

// 定义抽象产品
class Shape {
draw() {
throw new Error('draw method must be implemented')
}
}

// 定义具体产品
class Circle extends Shape {
draw() {
console.log('Drawing circle...')
}
}

class Rectangle extends Shape {
draw() {
console.log('Drawing rectangle...')
}
}

// 使用工厂创建产品
const factory = new BasicShapeFactory()
const shape = factory.createShape('circle')
shape.draw() // 输出: Drawing circle...
  1. 还有一个常见的例子是使用抽象工厂模式来创建不同的数据访问对象(DAO)。
    DAO 是 Data Access Object 的缩写,意思是数据访问对象。

它是一种设计模式,主要用于封装数据库访问相关的代码,使得应用程序可以通过统一的接口来访问不同类型的数据库。

DAO 的目的是将数据库的访问与业务逻辑分离开来,使得业务逻辑可以与不同的数据库无缝切换。这样,如果你希望将应用程序从一种数据库迁移到另一种数据库,你只需要修改 DAO 层的代码即可,而无需修改业务逻辑层的代码。
假设你正在开发一个应用,该应用可以使用不同的数据库类型来存储数据,比如 MySQL、PostgreSQL 和 MongoDB。在这种情况下,你可以使用抽象工厂模式来创建不同类型的数据访问对象。

下面是一个使用抽象工厂模式创建数据访问对象的示例:

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
// 定义抽象工厂
class DaoFactory {
createDao(type) {
throw new Error('createDao method must be implemented')
}
}

// 定义具体工厂
class MySQLDaoFactory extends DaoFactory {
createDao(type) {
switch (type) {
case 'user':
return new MySQLUserDao()
case 'product':
return new MySQLProductDao()
default:
throw new Error('Invalid DAO type')
}
}
}

class PostgreSQLDaoFactory extends DaoFactory {
createDao(type) {
switch (type) {
case 'user':
return new PostgreSQLUserDao()
case 'product':
return new PostgreSQLProductDao()
default:
throw new Error('Invalid DAO type')
}
}
}

class MongoDBDaoFactory extends DaoFactory {
createDao(type) {
switch (type) {
case "user":
return new MongoDBUserDao()
case "product":
return new MongoDBProductDao()
default:
throw new Error("Invalid DAO type")
}
}
}
// 定义抽象产品
class Dao {
save() {
throw new Error("save method must be implemented")
}

find() {
throw new Error("find method must be implemented")
}
}

// 定义具体产品
class MySQLUserDao extends Dao {
save() {
console.log("Saving user to MySQL database...")
}

find() {
console.log("Finding user in MySQL database...")
}
}

class MySQLProductDao extends Dao {
save() {
console.log("Saving product to MySQL database...")
}

find() {
console.log("Finding product in MySQL database...")
}
}

class PostgreSQLUserDao extends Dao {
save() {
console.log("Saving user to PostgreSQL database...")
}

find() {
console.log("Finding user in PostgreSQL database...")
}
}


class PostgreSQLProductDao extends Dao {
save() {
console.log("Saving product to PostgreSQL database...")
}
find() {
console.log("Finding product in PostgreSQL database...")
}
}

class MongoDBUserDao extends Dao {
save() {
console.log("Saving user to MongoDB database...")
}

find() {
console.log("Finding user in MongoDB database...")
}
}

class MongoDBProductDao extends Dao {
save() {
console.log("Saving product to MongoDB database...")
}

find() {
console.log("Finding product in MongoDB database...")
}
}

// 使用工厂创建产品
const factory = new MySQLDaoFactory()
const dao = factory.createDao('user')
dao.save() // 输出: Saving user to MySQL database...

在这个示例中,我们定义了一个抽象工厂 DaoFactory 和三个具体工厂 MySQLDaoFactory、PostgreSQLDaoFactory 和 MongoDBDaoFactory,以及一个抽象产品 Dao 和六个具体产品。然后,我们使用 MySQLDaoFactory 实例来创建数据访问对象,最后我们调用数据访问对象的 save 方法来保存数据。

这样,我们就可以使用抽象工厂模式来创建不同类型的数据访问对象,而无需关心具体的实现细节。如果需要增加新的数据库类型,只需要添加新的工厂类和数据访问对象类即可。