0%

设计模式之抽象工厂模式(Abstract Factory)

一、动机

在软件系统中,经常面临”一系列相互依赖的对象“的建构工作,同时,由于需求的变化,往往存在更多系列对象的创建工作。如何应对这种变化? 如何绕过常规的对象创建方法(new),提供一种”封装机制“来避免客户程序和这种”多系列具体对象创建工作“的紧耦合?

抽象工厂模式是一种创建型设计模式,它能创建一系列相关的对象,而无需指定其具体类。

二、解决方案

首先,抽象工厂模式建议为系列中的每件产品明确声明接口 。然后,确保所有产品变体都继承这些接口。接下来,我们需要声明抽象工厂——包含系列中所有产品构造方法的接口。

那么该如何处理产品变体呢?对于系列产品的每个变体,我们都将基于抽象工厂接口创建不同的工厂类。每个工厂类都只能返回特定类别的产品。

假设客户端想要工厂创建一把椅子。客户端无需了解工厂类,也不用管工厂类创建出的椅子类型。 无论是现代风格,还是维多利亚风格的椅子,对于客户端来说没有分别,它只需调用抽象椅子接口就可以了。这样一来,客户端只需知道椅子以某种方式实现了sit­坐下方法就足够了。此外,无论工厂返回的是何种椅子变体,它都会和由同一工厂对象创建的沙发风格一致。

如果客户端仅接触抽象接口,那么谁来创建实际的工厂对象呢? 一般情况下,应用程序会在初始化阶段创建具体工厂对象。而在此之前,应用程序必须根据配置文件或环境设定选择工厂类别。

如果没有应对”多系列对象构建“的需求变化,则没有必要使用Abstract Factory模式,这时候使用简单的工厂模式完全可以。”系列对象“指的是在某一特定系列下的对象之间有相互依赖,或作用的关系。不同系列的对象之间不能互相依赖。Abstract Factory模式主要在于应对”新系列“的需求变动。其缺点在于难以应对“新对象”的需求变动。

三、模板模式的结构

structure_of_Abstract_Factory

  1. 抽象产品Abstract Product)为构成系列产品的一组不同但相关的产品声明接口。
  2. 具体产品Concrete Product)是抽象产品的多种不同类型实现。所有变体(维多利亚/现代)都必须实现相应的抽象产品(椅子/沙发)。
  3. 抽象工厂Abstract Factory)接口声明了一组创建各种抽象产品的方法。
  4. 具体工厂Concrete Factory)实现抽象工厂的构建方法。每个具体工厂都对应特定产品变体,且仅创建此种产品变体。
  5. 尽管具体工厂会对具体产品进行初始化,其构建方法签名必须返回相应的抽象产品。这样,使用工厂类的客户端代码就不会与工厂创建的特定产品变体耦合。客户端Client)只需通过抽象接口调用工厂和产品对象,就能与任何具体工厂/产品变体交互。
四、Abstract Factory代码示例

假设你正在开发一款家具商店模拟器。你的代码中包括一些类,用于表示:

  1. 一系列相关产品,例如椅子Chair沙发Sofa
  2. 系列产品的不同变体。例如,你可以使用现代Modern维多利亚Victorian装饰风艺术Art­Deco等风格生成椅子沙发

Product_families_and_their_variants

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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#include <iostream>

// 椅子基类
class Chair {
public:
virtual ~Chair() {}
// 声明 坐下 的抽象方法
// 椅子系列的每个不同椅子都应具有的基本接口。所有椅子的变体必须实现此接口。
virtual void sit() = 0;
};

// 装饰风艺术风格的椅子
class ArtDecoChair : public Chair {
public:
void sit() override {
std::cout << "ArtDecoChair::sit()" << std::endl;
}
};

// 现代风格的椅子
class ModernChair : public Chair {
public:
void sit() override {
std::cout << "ModernChair::sit()" << std::endl;
}
};

// 维多利亚风格的椅子
class VictorianChair : public Chair {
public:
void sit() override {
std::cout << "VictorianChair::sit()" << std::endl;
}
};

// 沙发基类
class Sofa {
public:
virtual ~Sofa() {}
// 声明 躺下 的抽象方法
// 沙发系列的每个不同椅子都应具有的基本接口。所有沙发的变体必须实现此接口。
virtual void lie() = 0;
};

// 装饰风艺术风格的沙发
class ArtDecoSofa : public Sofa {
public:
void lie() override {
std::cout << "ArtDecoSofa::lie()" << std::endl;
}
};

// 现代风格的沙发
class ModernSofa : public Sofa {
public:
void lie() override {
std::cout << "ModernSofa::lie()" << std::endl;
}
};

// 维多利亚风格的沙发
class VictorianSofa : public Sofa {
public:
void lie() override {
std::cout << "VictorianSofa::lie()" << std::endl;
}
};

// 抽象工厂接口声明了一组能返回不同抽象产品的方法。这些产品属于同一个系列
// 且在高层主题或概念上具有相关性。同系列的产品通常能相互搭配使用。
// 系列产品可有多个变体,但不同变体的产品不能搭配使用。
class AbstractFactory {
public:
virtual ~AbstractFactory() {}
virtual Chair *createChair() = 0;
virtual Sofa *createSofa() = 0;
};

// 装饰风艺术风格产品工厂
// 具体工厂可生成属于同一变体的系列产品。工厂会确保其创建的产品能相互搭配
// 使用。具体工厂方法声明会返回一个抽象产品,但在方法内部则会对具体产品进
// 行实例化。
class ArtDecoFactory : public AbstractFactory {
public:
Chair *createChair() override {
std::cout << "ArtDecoChair created." << std::endl;
return new ArtDecoChair;
}

Sofa *createSofa() override {
std::cout << "ArtDecoSofa created." << std::endl;
return new ArtDecoSofa;
}
};

// 现代风格产品工厂、
// 每个具体工厂中都会包含一个相应的产品变体。
class ModernFactory : public AbstractFactory {
public:
Chair *createChair() override {
std::cout << "ModernChair created." << std::endl;
return new ModernChair;
}

Sofa *createSofa() override {
std::cout << "ModernSofa created." << std::endl;
return new ModernSofa;
}
};

// 维多利亚风格产品工厂
// 每个具体工厂中都会包含一个相应的产品变体。
class VictorianFactory : public AbstractFactory {
public:
Chair *createChair() override {
std::cout << "VictorianChair created." << std::endl;
return new VictorianChair;
}

Sofa *createSofa() override {
std::cout << "VictorianSofa created." << std::endl;
return new VictorianSofa;
}
};

// 客户端代码仅通过抽象类型(AbstractFactory、Chair 和 Sofa)使用工厂
// 和产品。这让你无需修改任何工厂或产品子类就能将其传递给客户端代码。
void ClientCode(AbstractFactory &factory) {
Chair *chair = factory.createChair();
Sofa *sofa = factory.createSofa();
chair->sit();
sofa->lie();

delete chair;
delete sofa;
}

int main() {
// 程序会根据当前配置或环境设定选择工厂类型
// 并在运行时创建工厂(通常在初始化阶段)。
ModernFactory *mf = new ModernFactory;
ClientCode(*mf);
delete mf;

// std::cout << std::endl;

// VictorianFactory *vf = new VictorianFactory;
// ClientCode(*vf);
// delete vf;
}

Execution result:
ModernChair created.
ModernSofa created.
ModernChair::sit()
ModernSofa::lie()
五、使用场景
  • 如果代码需要与多个不同系列的相关产品交互,但是由于无法提前获取相关信息,或者出于对未来扩展性的考虑,你不希望代码基于产品的具体类进行构建,在这种情况下,你可以使用抽象工厂。

抽象工厂为你提供了一个接口, 可用于创建每个系列产品的对象。只要代码通过该接口创建对象,那么你就不会生成与应用程序已生成的产品类型不一致的产品。

  • 如果你有一个基于一组抽象方法的类,且其主要功能因此变得不明确,那么在这种情况下可以考虑使用抽象工厂模式。

在设计良好的程序中,每个类仅负责一件事。如果一个类与多种类型产品交互,就可以考虑将工厂方法抽取到独立的工厂类或具备完整功能的抽象工厂类中。

六、优缺点
  • 你可以确保同一工厂生成的产品相互匹配。
  • 你可以避免客户端和具体产品代码的耦合。
  • 单一职责原则。你可以将产品生成代码抽取到同一位置,使得代码易于维护。
  • 开闭原则。向应用程序中引入新产品变体时,你无需修改客户端代码。
  • 由于采用该模式需要向应用中引入众多接口和类, 代码可能会比之前更加复杂。
七、与其它模式的关系
  • 在许多设计工作的初期都会使用工厂方法模式(较为简单,而且可以更方便地通过子类进行定制),随后演化为使用抽象工厂模式原型模式生成器模式(更灵活但更加复杂)。
  • 生成器重点关注如何分步生成复杂对象。抽象工厂专门用于生产一系列相关对象。抽象工厂会马上返回产品,生成器则允许你在获取产品前执行一些额外构造步骤。
  • 抽象工厂模式通常基于一组工厂方法,但你也可以使用原型模式来生成这些类的方法。
  • 你可以将抽象工厂桥接模式搭配使用。如果由桥接定义的抽象只能与特定实现合作,这一模式搭配就非常有用。在这种情况下,抽象工厂可以对这些关系进行封装,并且对客户端代码隐藏其复杂性。
  • 抽象工厂生成器原型都可以用单例模式来实现。