004《Design Patterns: Elements of Reusable Object-Oriented Software》读书笔记
《Design Patterns: Elements of Reusable Object-Oriented Software》所有的设计模式:
1. 创建型模式(Creational Patterns)
关注对象创建机制,以灵活性和解耦为目标。
1.1 Abstract Factory(抽象工厂)
抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。该模式适用于系统需要独立于其产品的创建、组合和表示方式时。抽象工厂通常实现为一组工厂方法,每个方法负责创建不同种类的对象。例如,GUI库可能为不同操作系统(Windows/Mac)提供不同的按钮和窗口实现,而客户端代码只需与抽象工厂接口交互,无需关心具体实现。
1.2 Builder(建造者)
建造者模式将复杂对象的构造与其表示分离,使得同样的构建过程可以创建不同的表示。该模式特别适用于需要分步骤构建的复杂对象,或者当创建过程需要允许不同的对象表示时。建造者模式通常包含一个指导者(Director)类来管理构建过程,以及多个具体的建造者类来实现不同的构建方式。例如,构建一个HTML文档时,可能有的建造者生成带样式的版本,有的生成纯文本版本。
1.3 Factory Method(工厂方法)
工厂方法模式定义了一个创建对象的接口,但让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。该模式适用于类无法预知它必须创建的对象的类时,或者当类希望其子类指定它创建的对象时。典型的例子是框架类定义抽象创建方法,由应用子类实现具体创建逻辑。
1.4 Prototype(原型)
原型模式通过复制现有的实例来创建新实例,而不是通过新建类的方式。该模式特别适用于创建成本较高的对象,或者当系统需要独立于其产品的创建、构成和表示时。原型模式通常需要实现一个克隆方法,该方法执行对象的深拷贝或浅拷贝。例如,图形编辑器中的图形对象可能实现克隆方法来支持复制粘贴功能。
1.5 Singleton(单例)
单例模式确保一个类只有一个实例,并提供一个全局访问点。该模式适用于需要严格控制实例数量的场景,如数据库连接池、线程池或配置管理器。单例模式通常通过私有化构造函数和提供静态访问方法来实现。需要注意线程安全和延迟初始化等问题。
2. 结构型模式(Structural Patterns)
处理类和对象的组合,形成更大的结构。
2.1 Adapter(适配器)
适配器模式将一个类的接口转换成客户期望的另一个接口。适配器使得原本由于接口不兼容而不能一起工作的类可以一起工作。该模式适用于需要使用现有类但其接口不符合需求时。适配器可以有两种实现方式:类适配器(通过多重继承)和对象适配器(通过组合)。例如,当需要使用第三方库但其API与现有系统不匹配时,可以创建适配器类。
2.2 Bridge(桥接)
桥接模式将抽象部分与实现部分分离,使它们都可以独立地变化。该模式通过组合代替继承来解决类爆炸问题。桥接模式适用于需要在多个维度上扩展类层次结构时。例如,图形渲染系统可能有不同形状(圆形、方形)和不同渲染方式(矢量、光栅),使用桥接模式可以避免创建圆形矢量渲染、圆形光栅渲染等多个子类。
2.3 Composite(组合)
组合模式将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。该模式适用于表示对象的部分-整体层次结构时。例如,文件系统可以表示为文件和目录的组合,其中目录可以包含文件或其他目录,但客户端代码可以统一处理它们。
2.4 Decorator(装饰器)
装饰器模式动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式比生成子类更为灵活。该模式通过组合对象而不是继承来扩展功能。装饰器模式适用于需要在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责时。例如,Java的I/O流就大量使用了装饰器模式,可以在基础流上叠加缓冲、压缩等功能。
2.5 Facade(外观)
外观模式为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。该模式适用于需要为复杂子系统提供简单接口时,或者需要减少子系统与客户端之间的依赖时。例如,编译器可能包含词法分析、语法分析、语义分析等多个复杂模块,但可以提供一个Compiler类作为外观来简化使用。
2.6 Flyweight(享元)
享元模式运用共享技术有效地支持大量细粒度的对象。该模式通过共享对象来减少内存使用,特别适用于存在大量相似对象时。享元对象通常分为内部状态(可共享)和外部状态(不可共享)。例如,文本编辑器中的字符对象可以共享字体、大小等属性,而位置信息则作为外部状态单独存储。
2.7 Proxy(代理)
代理模式为其他对象提供一种代理以控制对这个对象的访问。代理模式适用于需要在访问对象时进行额外控制时。常见的代理类型包括:远程代理(为远程对象提供本地代表)、虚拟代理(延迟创建开销大的对象)、保护代理(控制访问权限)和智能引用代理(添加额外操作)。例如,图片查看器可能使用虚拟代理来延迟加载大图片。
3. 行为型模式(Behavioral Patterns)
关注对象间的通信和职责分配。
3.1 Chain of Responsibility(责任链)
责任链模式使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。该模式适用于多个对象可以处理同一请求但具体处理者在运行时才能确定时。例如,异常处理机制或GUI事件传播机制可以使用责任链模式。
3.2 Command(命令)
命令模式将请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。该模式适用于需要将请求调用者与请求执行者解耦时,或者需要支持事务、撤销/重做等功能时。例如,文本编辑器的编辑操作可以封装为命令对象,支持撤销功能。
3.3 Interpreter(解释器)
解释器模式给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。该模式适用于需要解释特定领域语言时。解释器模式通常需要定义抽象语法树和解释器类。例如,正则表达式解释器可以使用解释器模式实现。
3.4 Iterator(迭代器)
迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。该模式适用于需要以统一方式遍历不同聚合结构时,或者需要支持多种遍历方式时。迭代器模式将遍历算法与聚合对象分离。例如,Java的Collection框架就广泛使用了迭代器模式。
3.5 Mediator(中介者)
中介者模式用一个中介对象来封装一系列对象之间的交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。该模式适用于对象之间存在复杂的网状引用关系时。例如,GUI组件之间的交互可以通过对话框对象作为中介者来协调。
3.6 Memento(备忘录)
备忘录模式在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。该模式适用于需要提供快照功能或撤销功能时。备忘录模式需要注意状态存储的效率问题。例如,文本编辑器的撤销功能可以使用备忘录模式实现。
3.7 Observer(观察者)
观察者模式定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。该模式适用于一个对象的改变需要同时改变其他对象时,或者当对象应该能够在不知道其他对象细节的情况下通知它们时。例如,MVC架构中的模型-视图关系可以使用观察者模式实现。
3.8 State(状态)
状态模式允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。该模式适用于对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时。状态模式将状态相关的行为局部化,并将状态转换显式化。例如,TCP连接的不同状态(建立、监听、关闭)可以使用状态模式实现。
3.9 Strategy(策略)
策略模式定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。策略模式使得算法可独立于使用它的客户而变化。该模式适用于需要在不同算法之间灵活切换时,或者当存在许多相关类仅在行为上有区别时。例如,排序算法可以封装为策略对象,根据需要在运行时切换。
3.10 Template Method(模板方法)
模板方法模式定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。该模式适用于多个类包含相似的算法,但某些步骤的实现不同时。例如,框架中的标准流程可以定义为模板方法,具体步骤由应用实现。
3.11 Visitor(访问者)
访问者模式表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。该模式适用于需要对复杂对象结构执行多种不相关的操作时。访问者模式将操作集中在一个访问者对象中,而不是分散在各个元素类中。例如,编译器中的语法树分析可以使用访问者模式实现不同类型的检查。
1. Design Pattern 1: Abstract Factory(抽象工厂)
1.1 意图(Intent)
抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
1.2 别名(Also Known As)
⚝ Kit
1.3 动机(Motivation)
考虑一个创建不同风格的用户界面工具包的场景。例如,你可能需要支持两种外观风格:一种是现代风格,另一种是复古风格。对于每种风格,你都需要创建一系列相关的 UI 元素,例如按钮(Button)、文本框(TextBox)和复选框(Checkbox)。
如果直接实例化具体的按钮、文本框和复选框类,那么你的代码将会与特定的外观风格紧密耦合。当你需要切换到另一种风格时,你不得不修改大量的代码。
抽象工厂模式通过定义一个创建相关产品族的接口来解决这个问题。每个具体工厂都实现这个接口,并负责创建特定风格的产品。客户端代码通过抽象工厂接口与工厂交互,而无需知道具体的工厂类。这样,就可以在运行时轻松地切换不同的产品族。
1.4 适用性(Applicability)
在以下情况下可以使用抽象工厂模式:
① 系统需要独立于它的产品创建、组合和表示方式。
▮▮▮▮ⓐ 当一个系统要由多个产品系列中的一个来配置时。
▮▮▮▮ⓑ 当你强调一系列相关的产品对象(即属于同一产品族)一起使用时,需要确保它们的约束性。
▮▮▮▮ⓒ 当你想提供一个产品类的库,并且只想显示它们的接口而不是实现时。
1.5 结构(Structure)
下面是抽象工厂模式的典型结构:
1
classDiagram
2
class AbstractFactory {
3
<<abstract>>
4
+createButton() AbstractButton*
5
+createTextBox() AbstractTextBox*
6
+createCheckbox() AbstractCheckbox*
7
}
8
class ConcreteFactory1 {
9
+createButton() ConcreteButton1*
10
+createTextBox() ConcreteTextBox1*
11
+createCheckbox() ConcreteCheckbox1*
12
}
13
class ConcreteFactory2 {
14
+createButton() ConcreteButton2*
15
+createTextBox() ConcreteTextBox2*
16
+createCheckbox() ConcreteCheckbox2*
17
}
18
class AbstractProductA {
19
<<abstract>>
20
+usefulFunctionA()
21
}
22
class ConcreteProductA1 {
23
+usefulFunctionA()
24
}
25
class ConcreteProductA2 {
26
+usefulFunctionA()
27
}
28
class AbstractProductB {
29
<<abstract>>
30
+usefulFunctionB()
31
+anotherUsefulFunctionB(AbstractProductA)
32
}
33
class ConcreteProductB1 {
34
+usefulFunctionB()
35
+anotherUsefulFunctionB(AbstractProductA)
36
}
37
class ConcreteProductB2 {
38
+usefulFunctionB()
39
+anotherUsefulFunctionB(AbstractProductA)
40
}
41
class Client {
42
+Client(AbstractFactory*)
43
+run()
44
}
45
46
AbstractFactory <|-- ConcreteFactory1
47
AbstractFactory <|-- ConcreteFactory2
48
AbstractProductA <|-- ConcreteProductA1
49
AbstractProductA <|-- ConcreteProductA2
50
AbstractProductB <|-- ConcreteProductB1
51
AbstractProductB <|-- ConcreteProductB2
52
ConcreteFactory1 ..> ConcreteProductA1
53
ConcreteFactory1 ..> ConcreteProductB1
54
ConcreteFactory2 ..> ConcreteProductA2
55
ConcreteFactory2 ..> ConcreteProductB2
56
Client ..> AbstractFactory
57
Client ..> AbstractProductA
58
Client ..> AbstractProductB
① AbstractFactory(抽象工厂):
▮▮▮▮ⓐ 声明创建抽象产品对象的方法的接口。
② ConcreteFactory(具体工厂):
▮▮▮▮ⓐ 实现抽象工厂接口,创建具体产品类的实例。每个具体工厂对应一个产品族。
③ AbstractProduct(抽象产品):
▮▮▮▮ⓐ 为一类产品对象声明接口。
④ ConcreteProduct(具体产品):
▮▮▮▮ⓐ 定义由相应的具体工厂创建的产品对象。每个具体产品对应一个具体工厂。
⑤ Client(客户端):
▮▮▮▮ⓐ 仅通过抽象工厂和抽象产品的接口来使用产品。
1.6 参与者(Participants)
① AbstractFactory (GUIFactory)
▮▮▮▮ⓐ 声明创建抽象产品对象的操作接口。
② ConcreteFactory (WinFactory, MotifFactory)
▮▮▮▮ⓐ 实现创建具体产品对象的操作,每个具体工厂创建一个产品族。
③ AbstractProduct (Button, TextBox, Checkbox)
▮▮▮▮ⓐ 为一类产品对象声明接口。
④ ConcreteProduct (WinButton, MotifButton, WinTextBox, MotifTextBox, ...)
▮▮▮▮ⓐ 定义一个由相应的具体工厂创建的产品对象。
⑤ Client
▮▮▮▮ⓐ 仅通过 AbstractFactory 和 AbstractProduct 接口声明的对象来使用。
1.7 协作(Collaborations)
① 通常在应用程序的配置阶段创建一个 ConcreteFactory 类的实例。应用程序只需要创建一个具体工厂实例,并且在整个应用程序生命周期中使用这个实例来创建相关的产品对象。
② AbstractFactory 将创建产品对象的责任委托给它的 ConcreteFactory 子类。
1.8 效果(Consequences)
抽象工厂模式有以下优点和缺点:
① 优点:
▮▮▮▮ⓐ 它支持“开闭原则”。 你可以通过引入新的具体工厂和产品族来扩展系统,而无需修改现有的客户端代码。
▮▮▮▮ⓑ 它使得易于交换产品系列。 只需改变具体工厂的实例,就可以让客户端使用不同的产品族。
▮▮▮▮ⓒ 它确保产品族中的产品一致性。 当一个产品族中的一个产品对象被创建出来之后,这个族中的其他产品对象也应该被创建出来,并且它们之间应该保持一致性。抽象工厂模式可以确保这一点。
② 缺点:
▮▮▮▮ⓐ 难以支持新种类的产品。 如果你需要添加一个新的产品到现有的产品族中,你可能需要修改抽象工厂接口及其所有的具体工厂。这可能会导致较大的改动。
1.9 实现(Implementation)
以下是一个用 C++ 实现抽象工厂模式的示例,模拟了创建不同风格的 GUI 元素(按钮和文本框):
1
#include <iostream>
2
#include <string>
3
4
// 抽象产品接口:按钮
5
class Button {
6
public:
7
virtual ~Button() = default;
8
virtual std::string render() const = 0;
9
};
10
11
// 具体产品:Windows 按钮
12
class WindowsButton : public Button {
13
public:
14
std::string render() const override {
15
return "Render a Windows style button.";
16
}
17
};
18
19
// 具体产品:Mac 按钮
20
class MacButton : public Button {
21
public:
22
std::string render() const override {
23
return "Render a Mac style button.";
24
}
25
};
26
27
// 抽象产品接口:文本框
28
class TextBox {
29
public:
30
virtual ~TextBox() = default;
31
virtual std::string render() const = 0;
32
};
33
34
// 具体产品:Windows 文本框
35
class WindowsTextBox : public TextBox {
36
public:
37
std::string render() const override {
38
return "Render a Windows style text box.";
39
}
40
};
41
42
// 具体产品:Mac 文本框
43
class MacTextBox : public TextBox {
44
public:
45
std::string render() const override {
46
return "Render a Mac style text box.";
47
}
48
};
49
50
// 抽象工厂接口
51
class GUIFactory {
52
public:
53
virtual ~GUIFactory() = default;
54
virtual Button* createButton() = 0;
55
virtual TextBox* createTextBox() = 0;
56
};
57
58
// 具体工厂:Windows 工厂
59
class WindowsFactory : public GUIFactory {
60
public:
61
Button* createButton() override {
62
return new WindowsButton();
63
}
64
TextBox* createTextBox() override {
65
return new WindowsTextBox();
66
}
67
};
68
69
// 具体工厂:Mac 工厂
70
class MacFactory : public GUIFactory {
71
public:
72
Button* createButton() override {
73
return new MacButton();
74
}
75
TextBox* createTextBox() override {
76
return new MacTextBox();
77
}
78
};
79
80
// 客户端代码
81
void application(GUIFactory& factory) {
82
Button* button = factory.createButton();
83
TextBox* textBox = factory.createTextBox();
84
85
std::cout << button->render() << std::endl;
86
std::cout << textBox->render() << std::endl;
87
88
delete button;
89
delete textBox;
90
}
91
92
int main() {
93
// 创建 Windows 风格的 UI 元素
94
WindowsFactory windowsFactory;
95
std::cout << "Client: Testing client code with the first factory type (Windows):" << std::endl;
96
application(windowsFactory);
97
std::cout << std::endl;
98
99
// 创建 Mac 风格的 UI 元素
100
MacFactory macFactory;
101
std::cout << "Client: Testing the same client code with the second factory type (Mac):" << std::endl;
102
application(macFactory);
103
104
return 0;
105
}
代码解释:
① 抽象产品接口 (Button
, TextBox
): 定义了按钮和文本框这类产品的通用接口。每个具体的产品类都必须实现这些接口。
② 具体产品 (WindowsButton
, MacButton
, WindowsTextBox
, MacTextBox
): 实现了抽象产品接口的具体类。例如,WindowsButton
提供了 Windows 风格按钮的具体实现。
③ 抽象工厂接口 (GUIFactory
): 声明了创建抽象产品的方法。在这个例子中,它声明了创建 Button
和 TextBox
的方法。
④ 具体工厂 (WindowsFactory
, MacFactory
): 实现了抽象工厂接口,负责创建特定产品族的对象。WindowsFactory
创建 WindowsButton
和 WindowsTextBox
的实例,而 MacFactory
创建 MacButton
和 MacTextBox
的实例。
⑤ 客户端 (application
函数和 main
函数): 客户端代码通过抽象工厂接口与工厂交互。在 main
函数中,我们创建了 WindowsFactory
和 MacFactory
的实例,并将它们传递给 application
函数。application
函数不关心具体创建的是哪种风格的按钮和文本框,它只知道通过抽象工厂创建并使用它们。
1.10 已知应用(Known Uses)
抽象工厂模式在许多框架和库中都有应用,例如:
⚝ GUI 工具包: 如 Qt 和 wxWidgets,它们通常使用抽象工厂来支持不同的平台外观。
⚝ 数据库访问组件: 可以使用抽象工厂来创建不同数据库系统的连接对象。
⚝ XML 解析器: 可以使用抽象工厂来创建不同类型的 XML 解析器。
1.11 相关模式(Related Patterns)
① Factory Method(工厂方法): 工厂方法通常用于创建单一类型的对象,而抽象工厂创建的是一系列相关的对象。抽象工厂通常在每个工厂方法中调用多个工厂方法。
② Builder(建造者): 建造者模式用于创建复杂的对象,它允许你一步一步地构建对象,并允许你为对象的每个部分选择不同的实现。抽象工厂创建的是一系列相关的对象,而不需要指定它们的组装方式。
③ Prototype(原型): 原型模式通过复制现有的对象来创建新的对象。抽象工厂创建的是全新的对象。
2. Design Pattern 2: Builder(建造者)
2.1 意图(Intent)
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
2.2 别名(Also Known As)
⚝ None commonly known.
2.3 动机(Motivation)
假设你需要创建一个表示文档的对象。这个文档可以有很多不同的格式(例如,纯文本、HTML、PDF)。文档的构建过程涉及到多个步骤,例如添加标题、添加段落、添加图片等。如果将文档的构建过程直接放在文档类的构造函数中,那么当文档的格式或者构建步骤发生变化时,就需要修改文档类。
建造者模式通过将文档的构建过程封装在一个独立的建造者类中来解决这个问题。建造者类定义了构建文档的各个部分的接口。具体的建造者类负责实现这些接口,并创建特定格式的文档。客户端代码通过指挥者(Director)来指导建造者构建文档,而无需知道具体的构建细节。这样,就可以在不修改文档类的情况下,创建不同格式的文档。
2.4 适用性(Applicability)
在以下情况下可以使用建造者模式:
① 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。
② 当构造过程必须允许被构造的对象有不同的表示时。
2.5 结构(Structure)
1
classDiagram
2
class Builder {
3
<<abstract>>
4
+buildPartA()
5
+buildPartB()
6
+getResult() Product*
7
}
8
class ConcreteBuilder1 {
9
+buildPartA()
10
+buildPartB()
11
+getResult() Product1*
12
}
13
class ConcreteBuilder2 {
14
+buildPartA()
15
+buildPartB()
16
+getResult() Product2*
17
}
18
class Director {
19
+construct()
20
}
21
class Product {
22
+parts
23
}
24
class Product1 {
25
+parts
26
}
27
class Product2 {
28
+parts
29
}
30
31
Builder <|-- ConcreteBuilder1
32
Builder <|-- ConcreteBuilder2
33
Director ..> Builder
34
ConcreteBuilder1 ..> Product1
35
ConcreteBuilder2 ..> Product2
① Builder(抽象建造者):
▮▮▮▮ⓐ 为创建一个 Product 对象的各个部件指定抽象接口。
② ConcreteBuilder(具体建造者):
▮▮▮▮ⓐ 实现 Builder 的接口以构造和装配该产品的各个部件。
▮▮▮▮ⓑ 定义并维护它所创建的表示。
▮▮▮▮ⓒ 提供一个检索产品的接口。
③ Director(指挥者):
▮▮▮▮ⓐ 构造一个使用 Builder 接口的对象。它主要是用于封装构建复杂对象的步骤。
④ Product(产品):
▮▮▮▮ⓐ 表示被构造的复杂对象。ConcreteBuilder 创建该产品的内部表示并定义它的装配过程。
▮▮▮▮ⓑ 包含定义组成部件的类,包括接口和实现。
2.6 参与者(Participants)
① Builder (TextConverter)
▮▮▮▮ⓐ 为创建一个 Product 对象的各个部件指定抽象接口。
② ConcreteBuilder (ASCIITextBuilder, RTFTextBuilder, TeXTextBuilder)
▮▮▮▮ⓐ 实现 Builder 的接口以构造和装配该产品的各个部件。
▮▮▮▮ⓑ 定义并维护它所创建的表示。
▮▮▮▮ⓒ 提供一个检索产品的接口(例如,GetASCIIText、GetRTFText)。
③ Director (TextEditor)
▮▮▮▮ⓐ 构造一个使用 Builder 接口的对象。
④ Product (ASCIIDocument, RTFDocument, TeXDocument)
▮▮▮▮ⓐ 表示被构造的复杂对象。ConcreteBuilder 创建该产品的内部表示并定义它的装配过程。
2.7 协作(Collaborations)
① 客户端创建一个 Director 对象,并用期望的 Builder 对象来配置它。
② Director 通过 Builder 接口调用建造者对象中的构建部件的方法。
③ Builder 接收到这些请求后,在 Director 的指导下逐步构建产品。
④ 一旦产品部件被创建完成,建造者就将产品返回给客户端。
2.8 效果(Consequences)
建造者模式有以下优点和缺点:
① 优点:
▮▮▮▮ⓐ 你可以改变一个产品的内部表示。 因为产品对象独立于产品的创建过程。
▮▮▮▮ⓑ 它将构造代码和表示代码分离。 客户端不需要知道产品内部是如何构建的。
▮▮▮▮ⓒ 可以对构造过程进行更精细的控制。 你可以定义构建的步骤和顺序。
② 缺点:
▮▮▮▮ⓐ 为了应对产品内部表示的变化,可能需要修改抽象建造者接口。 这可能会影响所有的具体建造者。
▮▮▮▮ⓑ 对于简单的产品,使用建造者模式可能会显得过于复杂。
2.9 实现(Implementation)
以下是一个用 C++ 实现建造者模式的示例,模拟了创建不同类型的汽车:
1
#include <iostream>
2
#include <string>
3
#include <vector>
4
5
// 产品类:汽车
6
class Car {
7
public:
8
void setEngine(const std::string& engine) { engine_ = engine; }
9
void setWheels(const std::string& wheels) { wheels_ = wheels; }
10
void setGPS(const std::string& gps) { gps_ = gps; }
11
12
void show() const {
13
std::cout << "Car with engine: " << engine_ << ", wheels: " << wheels_ << ", GPS: " << gps_ << std::endl;
14
}
15
16
private:
17
std::string engine_;
18
std::string wheels_;
19
std::string gps_;
20
};
21
22
// 抽象建造者
23
class CarBuilder {
24
public:
25
virtual ~CarBuilder() = default;
26
virtual void buildEngine() = 0;
27
virtual void buildWheels() = 0;
28
virtual void buildGPS() = 0;
29
virtual Car* getCar() = 0;
30
};
31
32
// 具体建造者:跑车建造者
33
class SportsCarBuilder : public CarBuilder {
34
public:
35
SportsCarBuilder() : car_(new Car()) {}
36
~SportsCarBuilder() override { delete car_; }
37
38
void buildEngine() override { car_->setEngine("Powerful Engine"); }
39
void buildWheels() override { car_->setWheels("Sport Wheels"); }
40
void buildGPS() override { car_->setGPS("High-Precision GPS"); }
41
Car* getCar() override { return car_; }
42
43
private:
44
Car* car_;
45
};
46
47
// 具体建造者:SUV 建造者
48
class SUVBuilder : public CarBuilder {
49
public:
50
SUVBuilder() : car_(new Car()) {}
51
~SUVBuilder() override { delete car_; }
52
53
void buildEngine() override { car_->setEngine("Standard Engine"); }
54
void buildWheels() override { car_->setWheels("All-Terrain Wheels"); }
55
void buildGPS() override { car_->setGPS("Basic GPS"); }
56
Car* getCar() override { return car_; }
57
58
private:
59
Car* car_;
60
};
61
62
// 指挥者
63
class CarDirector {
64
public:
65
void construct(CarBuilder* builder) {
66
builder->buildEngine();
67
builder->buildWheels();
68
builder->buildGPS();
69
}
70
};
71
72
int main() {
73
CarDirector director;
74
SportsCarBuilder sportsCarBuilder;
75
SUVBuilder suvBuilder;
76
77
std::cout << "Building a sports car:" << std::endl;
78
director.construct(&sportsCarBuilder);
79
Car* sportsCar = sportsCarBuilder.getCar();
80
sportsCar->show();
81
std::cout << std::endl;
82
83
std::cout << "Building an SUV:" << std::endl;
84
director.construct(&suvBuilder);
85
Car* suv = suvBuilder.getCar();
86
suv->show();
87
88
delete sportsCar;
89
delete suv;
90
91
return 0;
92
}
2.10 已知应用(Known Uses)
⚝ 文档生成器
⚝ 复杂对象创建,例如在游戏开发中创建角色或场景
⚝ 在一些框架中用于构建用户界面
2.11 相关模式(Related Patterns)
① Abstract Factory(抽象工厂): 抽象工厂用于创建相关对象的族,而建造者侧重于如何一步步构建一个复杂的对象。
② Factory Method(工厂方法): 工厂方法通常用于创建单一类型的对象,而建造者用于构建复杂的对象。
③ Composite(组合): 通常在建造者模式中构建的产品会使用组合模式。
3. Design Pattern 3: Factory Method(工厂方法)
3.1 意图(Intent)
定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method 使一个类的实例化延迟到其子类。
3.2 别名(Also Known As)
⚝ Virtual Constructor
3.3 动机(Motivation)
假设你需要创建一个应用程序来显示不同类型的文档(例如,图片、文本、视频)。你可能有一个基类 Document
和一些派生类 ImageDocument
, TextDocument
, VideoDocument
。当应用程序需要打开一个文档时,它需要根据文档的类型创建相应的文档对象。
如果直接在应用程序的代码中创建这些文档对象,那么当需要添加新的文档类型时,就需要修改应用程序的代码。工厂方法模式通过定义一个创建文档对象的抽象方法来解决这个问题。具体的子类可以重写这个方法来创建特定类型的文档对象。这样,应用程序只需要与抽象的文档类和工厂方法交互,而无需知道具体的文档类型。
3.4 适用性(Applicability)
在以下情况下可以使用工厂方法模式:
① 当一个类不知道它所必须创建的对象的类时。
② 当一个父类希望它的子类来指定它所创建的对象的类型时。
③ 当你想要集中控制对象的创建过程时。
3.5 结构(Structure)
1
classDiagram
2
class Creator {
3
<<abstract>>
4
+factoryMethod() Product*
5
+someOperation()
6
}
7
class ConcreteCreator1 {
8
+factoryMethod() ConcreteProduct1*
9
}
10
class ConcreteCreator2 {
11
+factoryMethod() ConcreteProduct2*
12
}
13
class Product {
14
<<abstract>>
15
+operation()
16
}
17
class ConcreteProduct1 {
18
+operation()
19
}
20
class ConcreteProduct2 {
21
+operation()
22
}
23
24
Creator <|-- ConcreteCreator1
25
Creator <|-- ConcreteCreator2
26
Product <|-- ConcreteProduct1
27
Product <|-- ConcreteProduct2
28
Creator ..> Product
① Product(产品):
▮▮▮▮ⓐ 定义工厂方法所创建的对象的接口。
② ConcreteProduct(具体产品):
▮▮▮▮ⓐ 实现 Product 接口。
③ Creator(创建者):
▮▮▮▮ⓐ 声明工厂方法,该方法返回一个 Product 类型的对象。Creator 也可以定义一个返回 Product 实例的缺省实现。
▮▮▮▮ⓑ Creator 可能定义一些依赖于 Product 对象的抽象操作。
④ ConcreteCreator(具体创建者):
▮▮▮▮ⓐ 重定义工厂方法以返回一个 ConcreteProduct 实例。
3.6 参与者(Participants)
① Product (Document)
▮▮▮▮ⓐ 定义工厂方法所创建的对象的接口。
② ConcreteProduct (ImageDocument, TextDocument)
▮▮▮▮ⓐ 实现 Product 接口。
③ Creator (Application)
▮▮▮▮ⓐ 声明工厂方法,该方法返回一个 Product 类型的对象。Creator 也可以定义一个返回 Product 实例的缺省实现。
④ ConcreteCreator (ImageApplication, TextApplication)
▮▮▮▮ⓐ 重定义工厂方法以返回一个 ConcreteProduct 实例。
3.7 协作(Collaborations)
① Creator 依赖于它的子类来定义工厂方法,因此它不会知道它所创建的 ConcreteProduct 的类。
② 通常,ConcreteCreator 重写工厂方法以便返回一个 ConcreteProduct 的实例。
3.8 效果(Consequences)
工厂方法模式有以下优点和缺点:
① 优点:
▮▮▮▮ⓐ 为创建对象定义了一个接口,但将实际创建推迟到子类。
▮▮▮▮ⓑ 使得代码更具灵活性和可扩展性。 可以很容易地添加新的产品类型,而无需修改现有的客户端代码。
▮▮▮▮ⓒ 符合“开闭原则”。
② 缺点:
▮▮▮▮ⓐ 可能会导致系统中类的数量增加。 对于每一个产品类型,可能都需要创建一个相应的具体创建者类。
3.9 实现(Implementation)
以下是一个用 C++ 实现工厂方法模式的示例,模拟了创建不同类型的日志记录器:
1
#include <iostream>
2
#include <string>
3
4
// 抽象产品接口:日志记录器
5
class Logger {
6
public:
7
virtual ~Logger() = default;
8
virtual void log(const std::string& message) = 0;
9
};
10
11
// 具体产品:控制台日志记录器
12
class ConsoleLogger : public Logger {
13
public:
14
void log(const std::string& message) override {
15
std::cout << "[Console]: " << message << std::endl;
16
}
17
};
18
19
// 具体产品:文件日志记录器
20
class FileLogger : public Logger {
21
public:
22
void log(const std::string& message) override {
23
std::cout << "[File]: " << message << " (logged to file)" << std::endl;
24
}
25
};
26
27
// 抽象创建者:日志记录器工厂
28
class LoggerFactory {
29
public:
30
virtual ~LoggerFactory() = default;
31
virtual Logger* createLogger() = 0;
32
void writeLog(const std::string& message) {
33
Logger* logger = createLogger();
34
logger->log(message);
35
delete logger;
36
}
37
};
38
39
// 具体创建者:控制台日志记录器工厂
40
class ConsoleLoggerFactory : public LoggerFactory {
41
public:
42
Logger* createLogger() override {
43
return new ConsoleLogger();
44
}
45
};
46
47
// 具体创建者:文件日志记录器工厂
48
class FileLoggerFactory : public LoggerFactory {
49
public:
50
Logger* createLogger() override {
51
return new FileLogger();
52
}
53
};
54
55
int main() {
56
ConsoleLoggerFactory consoleFactory;
57
consoleFactory.writeLog("This is a console log message.");
58
59
FileLoggerFactory fileFactory;
60
fileFactory.writeLog("This is a file log message.");
61
62
return 0;
63
}
3.10 已知应用(Known Uses)
⚝ 许多 GUI 工具包使用工厂方法来创建平台特定的控件。
⚝ 在测试框架中,可以使用工厂方法来创建模拟对象。
⚝ 日志记录框架通常使用工厂方法来创建不同类型的日志记录器。
3.11 相关模式(Related Patterns)
① Abstract Factory(抽象工厂): 抽象工厂通常使用一组工厂方法来创建产品族中的产品。
② Template Method(模板方法): 创建者类通常使用模板方法来定义对象的创建和处理流程,而将对象的实际创建委托给工厂方法。
4. Design Pattern 4: Prototype(原型)
4.1 意图(Intent)
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
4.2 别名(Also Known As)
⚝ None commonly known.
4.3 动机(Motivation)
在某些情况下,创建对象的实例可能非常耗时或复杂。例如,创建一个包含大量数据的对象,或者需要经过复杂的初始化过程的对象。在这种情况下,与其每次都重新创建对象,不如先创建一个原型对象,然后通过复制这个原型来创建新的对象。
原型模式允许你通过复制现有的对象来创建新的对象,而无需知道对象的具体类。这通常涉及到实现一个 Clone
操作。当你需要创建一个新对象时,你可以找到一个合适的原型对象,并调用其 Clone
方法来创建一个副本。
4.4 适用性(Applicability)
在以下情况下可以使用原型模式:
① 当一个系统应该独立于其产品创建、组成和表示的方式时;并且
② 当要实例化的类是在运行时指定时,例如,通过动态加载;或者
③ 为了避免创建一个与产品类的层次结构平行的工厂类的层次结构时;或者
④ 当一个类的实例只能有几种不同状态的组合时。通过安装相应数量的原型并克隆它们而不是每次都按请求实例化该类,可能更容易。
4.5 结构(Structure)
1
classDiagram
2
class Prototype {
3
<<abstract>>
4
+clone() Prototype*
5
}
6
class ConcretePrototype1 {
7
+clone() ConcretePrototype1*
8
}
9
class ConcretePrototype2 {
10
+clone() ConcretePrototype2*
11
}
12
class Client {
13
+operation()
14
}
15
16
Prototype <|-- ConcretePrototype1
17
Prototype <|-- ConcretePrototype2
18
Client ..> Prototype
① Prototype(原型):
▮▮▮▮ⓐ 声明一个克隆自身的接口。
② ConcretePrototype(具体原型):
▮▮▮▮ⓐ 实现克隆自身的操作。
③ Client(客户端):
▮▮▮▮ⓐ 通过要求原型克隆自身来创建一个新的对象。
4.6 参与者(Participants)
① Prototype (Graphic)
▮▮▮▮ⓐ 声明一个克隆自身的接口。
② ConcretePrototype (Line, Circle, Rectangle)
▮▮▮▮ⓐ 实现克隆自身的操作。
③ Client
▮▮▮▮ⓐ 通过要求原型克隆自身来创建一个新的对象。
4.7 协作(Collaborations)
① 客户端请求一个原型克隆自身。
② ConcretePrototype 负责实现克隆操作,通常会返回自身的一个新的副本。
4.8 效果(Consequences)
原型模式有以下优点和缺点:
① 优点:
▮▮▮▮ⓐ 减少了创建具有复杂内部结构的对象的工作量。
▮▮▮▮ⓑ 可以通过拷贝现有的实例来创建新的对象,而无需知道它们的具体类。
▮▮▮▮ⓒ 可以很容易地添加和删除原型。
▮▮▮▮ⓓ 可以通过不同的方式配置原型对象,从而创建出具有不同状态的对象。
② 缺点:
▮▮▮▮ⓐ 克隆复杂对象可能会比较困难,特别是当对象之间存在循环引用时。
▮▮▮▮ⓑ 每个 ConcretePrototype 子类都必须实现 Clone
操作。
4.9 实现(Implementation)
以下是一个用 C++ 实现原型模式的示例,模拟了创建不同类型的形状:
1
#include <iostream>
2
#include <string>
3
4
// 抽象原型类:形状
5
class Shape {
6
public:
7
virtual ~Shape() = default;
8
virtual Shape* clone() const = 0;
9
virtual void draw() const = 0;
10
};
11
12
// 具体原型类:圆形
13
class Circle : public Shape {
14
public:
15
Circle(int radius) : radius_(radius) {}
16
Circle(const Circle& other) : radius_(other.radius_) {} // 拷贝构造函数
17
18
Shape* clone() const override {
19
return new Circle(*this);
20
}
21
void draw() const override {
22
std::cout << "Drawing a circle with radius: " << radius_ << std::endl;
23
}
24
25
private:
26
int radius_;
27
};
28
29
// 具体原型类:矩形
30
class Rectangle : public Shape {
31
public:
32
Rectangle(int width, int height) : width_(width), height_(height) {}
33
Rectangle(const Rectangle& other) : width_(other.width_), height_(other.height_) {} // 拷贝构造函数
34
35
Shape* clone() const override {
36
return new Rectangle(*this);
37
}
38
void draw() const override {
39
std::cout << "Drawing a rectangle with width: " << width_ << " and height: " << height_ << std::endl;
40
}
41
42
private:
43
int width_;
44
int height_;
45
};
46
47
int main() {
48
Circle* circlePrototype = new Circle(10);
49
Rectangle* rectanglePrototype = new Rectangle(20, 30);
50
51
Shape* circle1 = circlePrototype->clone();
52
Shape* circle2 = circlePrototype->clone();
53
Shape* rectangle1 = rectanglePrototype->clone();
54
55
circle1->draw();
56
circle2->draw();
57
rectangle1->draw();
58
59
delete circlePrototype;
60
delete rectanglePrototype;
61
delete circle1;
62
delete circle2;
63
delete rectangle1;
64
65
return 0;
66
}
4.10 已知应用(Known Uses)
⚝ 在对象创建开销较大时,例如创建数据库连接或复杂的图形对象。
⚝ 在需要创建大量相似对象时。
⚝ 在某些框架中用于管理对象的状态。
4.11 相关模式(Related Patterns)
① Abstract Factory(抽象工厂): 抽象工厂可以用于创建产品族,而原型模式通过复制现有对象来创建新对象。它们可以一起使用。
② Factory Method(工厂方法): 工厂方法通常用于创建单一类型的对象,而原型模式可以创建任何类型的对象,只要它支持克隆操作。
5. Design Pattern 5: Singleton(单例)
5.1 意图(Intent)
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
5.2 别名(Also Known As)
⚝ None commonly known.
5.3 动机(Motivation)
在某些情况下,一个类只需要有一个实例存在。例如,一个系统中可能只需要一个配置管理器、一个日志记录器或者一个数据库连接池。如果允许创建多个这些类的实例,可能会导致资源浪费或者行为不一致。
单例模式通过确保一个类只有一个实例,并提供一个全局访问点来解决这个问题。通常,单例类会将自己的构造函数设为私有,以防止外部代码直接创建实例。它会提供一个静态方法来获取唯一的实例。
5.4 适用性(Applicability)
在以下情况下可以使用单例模式:
① 当一个类只能有一个实例而且客户必须从一个众所周知的访问点访问它时。
② 当这个唯一的实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。
5.5 结构(Structure)
1
classDiagram
2
class Singleton {
3
-instance : Singleton*
4
-Singleton()
5
+getInstance() : Singleton*
6
}
① Singleton(单例):
▮▮▮▮ⓐ 定义一个 getInstance
操作,允许客户访问它的唯一实例。getInstance
负责创建它自身的唯一实例。
5.6 参与者(Participants)
① Singleton (Singleton)
▮▮▮▮ⓐ 定义一个 getInstance
操作,允许客户访问它的唯一实例。
5.7 协作(Collaborations)
① 客户端通过调用 Singleton 的 getInstance
操作来访问 Singleton 的实例。
5.8 效果(Consequences)
单例模式有以下优点和缺点:
① 优点:
▮▮▮▮ⓐ 对唯一实例的受控访问。 Singleton 类封装了它的唯一实例,所以它可以严格地控制客户怎样以及何时访问它。
▮▮▮▮ⓑ 缩小了命名空间。 Singleton 模式是对全局变量的一种改进。它避免了那些存储唯一实例的全局变量污染命名空间。
▮▮▮▮ⓒ 允许对操作和表示的精化。 Singleton 类可能(如果需要的话)是可子类化的。这样就易于用 Singleton 的一个实例来配置一个应用。你可以用你所需的 Singleton 类的实例在运行时替换另一个实例。
② 缺点:
▮▮▮▮ⓐ 难以进行单元测试。 因为单例对象通常是全局的,所以很难在测试环境中替换掉它们。
▮▮▮▮ⓑ 可能违反单一职责原则。 单例类既要负责创建和管理自己的唯一实例,又要负责提供业务逻辑。
▮▮▮▮ⓒ 在多线程环境下需要特别注意线程安全问题。
5.9 实现(Implementation)
以下是一个用 C++ 实现单例模式的示例:
1
#include <iostream>
2
#include <mutex>
3
4
class Singleton {
5
public:
6
// 获取单例实例的静态方法
7
static Singleton* getInstance() {
8
// 使用双重检查锁定来保证线程安全
9
if (instance_ == nullptr) {
10
std::lock_guard<std::mutex> lock(mutex_);
11
if (instance_ == nullptr) {
12
instance_ = new Singleton();
13
}
14
}
15
return instance_;
16
}
17
18
// 防止拷贝构造和赋值运算符
19
Singleton(const Singleton&) = delete;
20
Singleton& operator=(const Singleton&) = delete;
21
22
void someOperation() {
23
std::cout << "Singleton instance is doing something." << std::endl;
24
}
25
26
private:
27
// 私有构造函数,防止外部创建实例
28
Singleton() {
29
std::cout << "Singleton instance created." << std::endl;
30
}
31
32
// 静态成员变量,用于存储唯一的实例
33
static Singleton* instance_;
34
// 用于线程安全的互斥锁
35
static std::mutex mutex_;
36
};
37
38
// 初始化静态成员变量
39
Singleton* Singleton::instance_ = nullptr;
40
std::mutex Singleton::mutex_;
41
42
int main() {
43
Singleton* instance1 = Singleton::getInstance();
44
Singleton* instance2 = Singleton::getInstance();
45
46
if (instance1 == instance2) {
47
std::cout << "Both instances are the same." << std::endl;
48
}
49
50
instance1->someOperation();
51
instance2->someOperation();
52
53
// 注意:单例对象的生命周期管理需要小心处理,避免内存泄漏。
54
// 在简单的例子中,可以在程序结束时手动删除,或者使用更复杂的生命周期管理机制。
55
// delete instance1; // 不推荐这样做,因为 instance2 也指向同一个对象
56
// instance1 = nullptr;
57
// instance2 = nullptr;
58
59
return 0;
60
}
代码解释:
① 私有构造函数: Singleton()
是私有的,这意味着外部代码无法直接创建 Singleton
类的实例。
② 静态成员变量 (instance_
): 用于存储 Singleton
类的唯一实例,初始值为 nullptr
。
③ 静态方法 (getInstance
): 这是获取 Singleton
实例的唯一方法。它首先检查 instance_
是否为空,如果为空,则创建一个新的 Singleton
实例并将其赋值给 instance_
。为了保证在多线程环境下的线程安全,使用了双重检查锁定和互斥锁 (mutex_
)。
④ 删除拷贝构造函数和赋值运算符: 通过 = delete
阻止外部代码拷贝或赋值 Singleton
对象,进一步保证了只有一个实例。
⑤ 线程安全: 使用 std::mutex
来确保在多线程环境下只有一个线程能够创建 Singleton
的实例。
5.10 已知应用(Known Uses)
⚝ 日志记录器
⚝ 配置管理器
⚝ 数据库连接池
⚝ 打印机假脱机程序
5.11 相关模式(Related Patterns)
⚝ Abstract Factory(抽象工厂), Builder(建造者), Prototype(原型): 这些模式可以使用单例模式来实现它们的具体工厂、建造者或原型管理器。
6. Design Pattern 6: Adapter(适配器)
6.1 意图(Intent)
将一个类的接口转换成客户希望的另外一个接口。Adapter 模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
6.2 别名(Also Known As)
⚝ Wrapper
6.3 动机(Motivation)
假设你有一个已经存在的类,它的接口与你当前系统需要的接口不兼容。你不能直接使用这个类,因为它的方法名、参数或者返回类型与你的系统期望的不同。
适配器模式允许你创建一个中间层(适配器),它包装了现有的类,并将它的接口转换成你的系统需要的接口。这样,你就可以在不修改现有类的情况下,使它能够与你的系统协同工作。
6.4 适用性(Applicability)
在以下情况下可以使用适配器模式:
① 当你想使用一个已经存在的类,而它的接口不符合你的需求时。
② 当你想创建一个可复用的类,该类可以与其他不相关的类(即那些具有不必要接口的类)协同工作时。
③ (仅对象适配器)当你需要使用几个现有的子类,但通过对它们的父类进行子类化不可能对它们的接口进行适配时。
6.5 结构(Structure)
对象适配器:
1
classDiagram
2
class Target {
3
<<interface>>
4
+request()
5
}
6
class Adapter {
7
+request()
8
}
9
class Adaptee {
10
+specificRequest()
11
}
12
13
Target <|-- Adapter
14
Adapter ..> Adaptee
类适配器(使用多重继承):
1
classDiagram
2
class Target {
3
<<interface>>
4
+request()
5
}
6
class Adapter {
7
+request()
8
+specificRequest()
9
}
10
class Adaptee {
11
+specificRequest()
12
}
13
14
Target <|-- Adapter
15
Adaptee <|-- Adapter
① Target(目标接口):
▮▮▮▮ⓐ 定义 Client 使用的与特定领域相关的接口。
② Client(客户):
▮▮▮▮ⓐ 与符合 Target 接口的对象(适配器)协同工作。
③ Adaptee(被适配者):
▮▮▮▮ⓐ 定义一个已经存在的接口,这个接口需要适配。
④ Adapter(适配器):
▮▮▮▮ⓐ 实现 Target 接口。
▮▮▮▮ⓑ (对象适配器)包含一个 Adaptee 类的实例。
▮▮▮▮ⓒ (类适配器)通过多重继承 Adaptee。
▮▮▮▮ⓓ 将 Target 接口的请求转换为 Adaptee 接口的调用。
6.6 参与者(Participants)
① Target (Shape)
▮▮▮▮ⓐ 定义 Client 使用的与特定领域相关的接口。
② Client (DrawingEditor)
▮▮▮▮ⓐ 与符合 Target 接口的对象(适配器)协同工作。
③ Adaptee (TextView)
▮▮▮▮ⓐ 定义一个已经存在的接口,这个接口需要适配。
④ Adapter (TextShape)
▮▮▮▮ⓐ 实现 Target 接口。
▮▮▮▮ⓑ 包含一个 Adaptee 类的实例。
▮▮▮▮ⓒ 将 Target 接口的请求转换为 Adaptee 接口的调用。
6.7 协作(Collaborations)
① 客户端通过 Target 接口调用适配器的方法。
② 适配器将这些调用转换为对被适配者对象的方法的调用。
③ 客户端不会直接与被适配者交互,而是通过适配器进行间接访问。
6.8 效果(Consequences)
适配器模式有以下优点和缺点:
① 优点:
▮▮▮▮ⓐ 提高了类的复用性。 可以使用那些原本由于接口不兼容而不能使用的类。
▮▮▮▮ⓑ 将接口与实现分离。 客户端代码只需要与目标接口交互,而不需要知道被适配者的具体实现。
▮▮▮▮ⓒ 符合“开闭原则”。 可以通过创建新的适配器来引入新的被适配者,而无需修改现有的客户端代码。
② 缺点:
▮▮▮▮ⓐ 可能会增加系统的复杂性。 引入适配器类会增加类的数量。
▮▮▮▮ⓑ (类适配器)C++ 中多重继承的使用需要谨慎。
6.9 实现(Implementation)
以下是一个用 C++ 实现对象适配器模式的示例,模拟了使用一个旧的文本渲染器在新的图形系统中显示文本:
1
#include <iostream>
2
#include <string>
3
4
// 旧的文本渲染器接口(被适配者)
5
class OldTextRenderer {
6
public:
7
void renderText(const std::string& text) {
8
std::cout << "[Old Renderer]: Rendering text: " << text << std::endl;
9
}
10
};
11
12
// 新的图形系统接口(目标接口)
13
class GraphicsRenderer {
14
public:
15
virtual ~GraphicsRenderer() = default;
16
virtual void drawText(const std::string& text) = 0;
17
};
18
19
// 适配器:将 OldTextRenderer 的接口适配到 GraphicsRenderer 的接口
20
class TextRendererAdapter : public GraphicsRenderer {
21
public:
22
TextRendererAdapter(OldTextRenderer* renderer) : oldRenderer_(renderer) {}
23
~TextRendererAdapter() override = default;
24
25
void drawText(const std::string& text) override {
26
oldRenderer_->renderText(text);
27
}
28
29
private:
30
OldTextRenderer* oldRenderer_;
31
};
32
33
// 客户端代码
34
void clientCode(GraphicsRenderer* renderer, const std::string& text) {
35
std::cout << "Client: I expect a graphic renderer..." << std::endl;
36
renderer->drawText(text);
37
}
38
39
int main() {
40
OldTextRenderer* oldRenderer = new OldTextRenderer();
41
TextRendererAdapter* adapter = new TextRendererAdapter(oldRenderer);
42
43
clientCode(adapter, "Hello, Adapter Pattern!");
44
45
delete oldRenderer;
46
delete adapter;
47
48
return 0;
49
}
6.10 已知应用(Known Uses)
⚝ 当需要集成遗留代码或者第三方库时,它们的接口可能与当前系统的接口不兼容。
⚝ 在不同的数据格式之间进行转换。
⚝ 在不同的 API 之间进行桥接。
6.11 相关模式(Related Patterns)
① Bridge(桥接): 桥接模式通常用于分离抽象接口和实现,而适配器模式用于改变现有接口以使其能够工作。
② Decorator(装饰器): 装饰器模式通过动态地给一个对象添加一些额外的职责,而适配器模式的目的是改变一个对象的接口。
③ Facade(外观): 外观模式为子系统中的一组接口提供一个统一的接口,而适配器模式的目的是改变单个类的接口。
7. Design Pattern 7: Bridge(桥接)
7.1 意图(Intent)
将抽象部分与它的实现部分分离,使它们都可以独立地变化。
7.2 别名(Also Known As)
⚝ None commonly known.
7.3 动机(Motivation)
考虑一个需要支持多种操作系统和多种窗口系统的场景。你可能有一个抽象的 Window
类,以及具体的 ApplicationWindow
和 IconWindow
类。同时,你可能需要支持不同的窗口系统,例如 X Window 和 Microsoft Windows。
如果直接使用继承来实现,你可能需要创建大量的子类,例如 XWindowApplicationWindow
, MSWindowApplicationWindow
, XWindowIconWindow
, MSWindowIconWindow
等。当需要添加新的操作系统或新的窗口类型时,类的数量会呈指数级增长。
桥接模式通过将抽象部分(例如,Window
)和实现部分(例如,窗口系统)分离来解决这个问题。抽象部分定义了高层的接口,而实现部分定义了底层的实现。抽象部分包含一个指向实现部分的引用,并将操作委托给实现部分。这样,抽象部分和实现部分可以独立地变化,而不会相互影响。
7.4 适用性(Applicability)
在以下情况下可以使用桥接模式:
① 当你想要在抽象和它的实现之间有一个固定的绑定关系时。这个实现可以在运行时刻选择。
② 当一个抽象和它的实现都应该可以通过子类化独立地进行扩展时。
③ 当对一个抽象的实现进行修改不应该影响到客户时。
7.5 结构(Structure)
1
classDiagram
2
class Abstraction {
3
+operation()
4
}
5
class RefinedAbstraction {
6
+operation()
7
}
8
class Implementor {
9
<<interface>>
10
+operationImpl()
11
}
12
class ConcreteImplementorA {
13
+operationImpl()
14
}
15
class ConcreteImplementorB {
16
+operationImpl()
17
}
18
19
Abstraction --o Implementor : implementor
20
RefinedAbstraction --o Implementor : implementor
21
Implementor <|-- ConcreteImplementorA
22
Implementor <|-- ConcreteImplementorB
23
RefinedAbstraction --|> Abstraction
① Abstraction(抽象):
▮▮▮▮ⓐ 定义抽象的接口。
▮▮▮▮ⓑ 维护一个指向 Implementor 类型对象的引用。
② RefinedAbstraction(精炼的抽象):
▮▮▮▮ⓐ Abstraction 的子类,扩展了由父类定义的接口。
③ Implementor(实现者接口):
▮▮▮▮ⓐ 定义实现类的接口。这个接口不一定要与 Abstraction 的接口完全一致;事实上这两个接口可以完全不同。通常情况下,Implementor 接口仅提供基本操作,而 Abstraction 则定义了基于这些基本操作的高层操作。
④ ConcreteImplementor(具体实现者):
▮▮▮▮ⓐ 实现 Implementor 接口并定义它的具体实现。
7.6 参与者(Participants)
① Abstraction (Window)
▮▮▮▮ⓐ 定义抽象的接口。
▮▮▮▮ⓑ 维护一个指向 Implementor 类型对象的引用。
② RefinedAbstraction (ApplicationWindow, IconWindow)
▮▮▮▮ⓐ Abstraction 的子类,扩展了由父类定义的接口。
③ Implementor (WindowImp)
▮▮▮▮ⓐ 定义实现类的接口。
④ ConcreteImplementor (XWindowImp, MSWindowImp)
▮▮▮▮ⓐ 实现 Implementor 接口并定义它的具体实现。
7.7 协作(Collaborations)
① Abstraction 将某些(或者全部)工作委托给 Implementor 对象。
7.8 效果(Consequences)
桥接模式有以下优点和缺点:
① 优点:
▮▮▮▮ⓐ 抽象和实现分离。 它们可以独立地变化。
▮▮▮▮ⓑ 提高了可扩展性。 可以很容易地添加新的抽象和新的实现。
▮▮▮▮ⓒ 实现细节对客户透明。 客户只需要与抽象接口交互。
② 缺点:
▮▮▮▮ⓐ 可能会增加系统的复杂性。 需要引入额外的类来表示抽象和实现之间的桥梁。
7.9 实现(Implementation)
以下是一个用 C++ 实现桥接模式的示例,模拟了不同形状在不同渲染器上的绘制:
1
#include <iostream>
2
#include <string>
3
4
// 实现者接口:渲染器
5
class Renderer {
6
public:
7
virtual ~Renderer() = default;
8
virtual void renderCircle(float x, float y, float radius) = 0;
9
virtual void renderRectangle(float x1, float y1, float x2, float y2) = 0;
10
};
11
12
// 具体实现者:控制台渲染器
13
class ConsoleRenderer : public Renderer {
14
public:
15
void renderCircle(float x, float y, float radius) override {
16
std::cout << "Console: Drawing circle at (" << x << ", " << y << ") with radius " << radius << std::endl;
17
}
18
void renderRectangle(float x1, float y1, float x2, float y2) override {
19
std::cout << "Console: Drawing rectangle from (" << x1 << ", " << y1 << ") to (" << x2 << ", " << y2 << ")" << std::endl;
20
}
21
};
22
23
// 具体实现者:OpenGL 渲染器
24
class OpenGLRenderer : public Renderer {
25
public:
26
void renderCircle(float x, float y, float radius) override {
27
std::cout << "OpenGL: Drawing circle at (" << x << ", " << y << ") with radius " << radius << std::endl;
28
}
29
void renderRectangle(float x1, float y1, float x2, float y2) override {
30
std::cout << "OpenGL: Drawing rectangle from (" << x1 << ", " << y1 << ") to (" << x2 << ", " << y2 << ")" << std::endl;
31
}
32
};
33
34
// 抽象接口:形状
35
class Shape {
36
public:
37
Shape(Renderer* renderer) : renderer_(renderer) {}
38
virtual ~Shape() = default;
39
virtual void draw() = 0;
40
41
protected:
42
Renderer* renderer_;
43
};
44
45
// 精炼的抽象:圆形
46
class CircleShape : public Shape {
47
public:
48
CircleShape(Renderer* renderer, float x, float y, float radius)
49
: Shape(renderer), x_(x), y_(y), radius_(radius) {}
50
void draw() override {
51
renderer_->renderCircle(x_, y_, radius_);
52
}
53
54
private:
55
float x_, y_, radius_;
56
};
57
58
// 精炼的抽象:矩形
59
class RectangleShape : public Shape {
60
public:
61
RectangleShape(Renderer* renderer, float x1, float y1, float x2, float y2)
62
: Shape(renderer), x1_(x1), y1_(y1), x2_(x2), y2_(y2) {}
63
void draw() override {
64
renderer_->renderRectangle(x1_, y1_, x2_, y2_);
65
}
66
67
private:
68
float x1_, y1_, x2_, y2_;
69
};
70
71
int main() {
72
Renderer* consoleRenderer = new ConsoleRenderer();
73
Renderer* openGLRenderer = new OpenGLRenderer();
74
75
Shape* circle1 = new CircleShape(consoleRenderer, 10, 10, 5);
76
Shape* rectangle1 = new RectangleShape(consoleRenderer, 20, 20, 40, 40);
77
Shape* circle2 = new CircleShape(openGLRenderer, 30, 30, 10);
78
Shape* rectangle2 = new RectangleShape(openGLRenderer, 50, 50, 70, 70);
79
80
circle1->draw();
81
rectangle1->draw();
82
circle2->draw();
83
rectangle2->draw();
84
85
delete consoleRenderer;
86
delete openGLRenderer;
87
delete circle1;
88
delete rectangle1;
89
delete circle2;
90
delete rectangle2;
91
92
return 0;
93
}
7.10 已知应用(Known Uses)
⚝ GUI 工具包,例如将窗口抽象与底层窗口系统实现分离。
⚝ 数据库驱动程序,例如将数据库操作抽象与具体的数据库实现分离。
⚝ 操作系统中的文件系统抽象。
7.11 相关模式(Related Patterns)
① Abstract Factory(抽象工厂): 抽象工厂可以创建不同实现的对象,而桥接模式则分离了抽象接口和实现。它们可以一起使用。
② Adapter(适配器): 适配器模式用于改变现有接口以使其能够工作,而桥接模式的目的是分离抽象接口和实现,使它们可以独立变化。
③ Decorator(装饰器): 装饰器模式动态地给一个对象添加一些额外的职责,而桥接模式的目的是分离抽象接口和实现。
8. Design Pattern 8: Composite(组合)
8.1 意图(Intent)
将对象组合成树状结构以表示“部分-整体”的层次结构。Composite 使得用户对单个对象和组合对象的使用具有一致性。
8.2 别名(Also Known As)
⚝ None commonly known.
8.3 动机(Motivation)
假设你需要表示一个组织结构,其中包含部门和员工。部门可以包含其他部门和员工,而员工是组织结构中的叶节点。你需要能够对整个组织结构执行操作,例如计算总薪资或者查找特定的员工。
组合模式允许你将部门和员工都看作是组织结构中的组件,并以统一的方式对待它们。你可以创建一个表示部门的组合对象,它包含其他部门和员工对象。然后,你可以对这个组合对象执行操作,它会自动将操作委托给其包含的所有子组件。这样,客户端代码就可以以一致的方式处理单个对象和组合对象。
8.4 适用性(Applicability)
在以下情况下可以使用组合模式:
① 当你想表示对象的部分-整体层次结构时。
② 当你希望用户忽略单个对象和组合对象之间的不同,并且能够以一致的方式对待它们时。
8.5 结构(Structure)
1
classDiagram
2
class Component {
3
<<abstract>>
4
+operation()
5
+add(Component*)
6
+remove(Component*)
7
+getChild(int) Component*
8
}
9
class Leaf {
10
+operation()
11
}
12
class Composite {
13
+operation()
14
+add(Component*)
15
+remove(Component*)
16
+getChild(int) Component*
17
}
18
19
Component <|-- Leaf
20
Component <|-- Composite
21
Composite --* Component : children
① Component(组件):
▮▮▮▮ⓐ 声明组合中对象的接口。
▮▮▮▮ⓑ 在适当的情况下,定义所有类共有的缺省行为。
▮▮▮▮ⓒ 声明用于访问和管理子组件的操作接口。
▮▮▮▮ⓓ (可选)在 Component 接口中定义一个父接口,并在实现中实现它。
② Leaf(叶节点):
▮▮▮▮ⓐ 表示组合中的叶节点对象。叶节点没有子节点。
▮▮▮▮ⓑ 实现 Component 接口。
③ Composite(组合):
▮▮▮▮ⓐ 定义有子组件的对象的行为。
▮▮▮▮ⓑ 存储子组件。
▮▮▮▮ⓒ 在 Component 接口中实现与子组件有关的操作,例如增加、删除和获取子组件。
▮▮▮▮ⓓ 实现 Component 接口中定义的其他操作,一般是将它们递归地应用到其子组件上。
8.6 参与者(Participants)
① Component (Graphic)
▮▮▮▮ⓐ 声明组合中对象的接口。
② Leaf (Rectangle, Circle, Line)
▮▮▮▮ⓐ 表示组合中的叶节点对象。
③ Composite (Picture)
▮▮▮▮ⓐ 定义有子组件的对象的行为。
▮▮▮▮ⓑ 存储子组件。
▮▮▮▮ⓒ 在 Component 接口中实现与子组件有关的操作。
8.7 协作(Collaborations)
① 客户端使用 Component 接口来操作组合中的对象。如果接收者是一个叶节点,那么操作就被直接执行。如果接收者是一个组合,那么它通常将操作递归地应用到其子组件上,在做一些额外的操作之前或之后。
8.8 效果(Consequences)
组合模式有以下优点和缺点:
① 优点:
▮▮▮▮ⓐ 定义了包含基本对象和组合对象的类层次结构。 基本对象可以被组合成更复杂的组合对象,而这些组合对象又可以被组合,依此类推。
▮▮▮▮ⓑ 使得客户可以一致地使用组合结构和单个对象。 客户通常不需要知道它们正在处理的是一个叶节点还是一个组合。这简化了客户代码。
▮▮▮▮ⓒ 使得更容易添加新的组件类型。 只要新的组件类实现了 Component 接口,它就可以很容易地添加到现有的结构中。
② 缺点:
▮▮▮▮ⓐ 有时会使得设计过于通用。 你可能会发现很难限制组合中的组件类型。有时你希望一个组合只包含某些特定的组件。组合模式并没有提供强制这种限制的机制。
8.9 实现(Implementation)
以下是一个用 C++ 实现组合模式的示例,模拟了文件系统中的目录和文件:
1
#include <iostream>
2
#include <string>
3
#include <vector>
4
5
// 抽象组件:文件系统条目
6
class FileSystemEntry {
7
public:
8
virtual ~FileSystemEntry() = default;
9
virtual std::string getName() const = 0;
10
virtual void print(int depth = 0) const = 0;
11
virtual void add(FileSystemEntry* entry) {}
12
virtual void remove(FileSystemEntry* entry) {}
13
virtual FileSystemEntry* getChild(int index) { return nullptr; }
14
};
15
16
// 叶节点:文件
17
class File : public FileSystemEntry {
18
public:
19
File(const std::string& name) : name_(name) {}
20
std::string getName() const override { return name_; }
21
void print(int depth) const override {
22
for (int i = 0; i < depth; ++i) {
23
std::cout << " ";
24
}
25
std::cout << "File: " << name_ << std::endl;
26
}
27
28
private:
29
std::string name_;
30
};
31
32
// 组合节点:目录
33
class Directory : public FileSystemEntry {
34
public:
35
Directory(const std::string& name) : name_(name) {}
36
std::string getName() const override { return name_; }
37
void print(int depth) const override {
38
for (int i = 0; i < depth; ++i) {
39
std::cout << " ";
40
}
41
std::cout << "Directory: " << name_ << std::endl;
42
for (const auto& entry : children_) {
43
entry->print(depth + 1);
44
}
45
}
46
void add(FileSystemEntry* entry) override {
47
children_.push_back(entry);
48
}
49
void remove(FileSystemEntry* entry) override {
50
// 简单的移除实现,实际应用中可能需要更复杂的逻辑
51
for (auto it = children_.begin(); it != children_.end(); ++it) {
52
if (*it == entry) {
53
children_.erase(it);
54
break;
55
}
56
}
57
}
58
FileSystemEntry* getChild(int index) override {
59
if (index >= 0 && index < children_.size()) {
60
return children_[index];
61
}
62
return nullptr;
63
}
64
65
private:
66
std::string name_;
67
std::vector<FileSystemEntry*> children_;
68
};
69
70
int main() {
71
Directory* root = new Directory("root");
72
Directory* documents = new Directory("documents");
73
Directory* pictures = new Directory("pictures");
74
75
File* report = new File("report.docx");
76
File* image1 = new File("image1.jpg");
77
File* image2 = new File("image2.png");
78
79
root->add(documents);
80
root->add(pictures);
81
82
documents->add(report);
83
pictures->add(image1);
84
pictures->add(image2);
85
86
root->print();
87
88
delete root;
89
delete documents;
90
delete pictures;
91
delete report;
92
delete image1;
93
delete image2;
94
95
return 0;
96
}
8.10 已知应用(Known Uses)
⚝ GUI 工具包中的控件层次结构。
⚝ 文件系统中的目录和文件。
⚝ 组织结构图。
8.11 相关模式(Related Patterns)
① Factory Method(工厂方法): 可以使用工厂方法来创建组合中的子组件。
② Iterator(迭代器): 可以使用迭代器来遍历组合结构。
③ Visitor(访问者): 访问者模式可以用于对组合结构中的对象执行操作。
9. Design Pattern 9: Decorator(装饰器)
9.1 意图(Intent)
动态地给一个对象添加一些额外的职责。就扩展性而言,Decorator 模式比生成子类方式更为灵活。
9.2 别名(Also Known As)
⚝ Wrapper
9.3 动机(Motivation)
假设你需要为一个文本编辑器添加各种不同的功能,例如添加边框、添加滚动条、添加阴影等。一种方法是为每种可能的组合创建一个子类,但这会导致大量的子类,并且难以管理。
装饰器模式通过将每个要添加的功能包装在一个装饰器对象中来解决这个问题。装饰器对象与原始对象具有相同的接口,并且包含一个指向原始对象的引用。装饰器对象在调用原始对象的方法之前或之后,可以添加自己的行为。这样,就可以在不修改原始对象的情况下,动态地组合不同的功能。
9.4 适用性(Applicability)
在以下情况下可以使用装饰器模式:
① 当需要动态地给一个对象添加额外的职责,而又不希望通过生成子类的方式进行扩展时。
② 当需要在一个对象上添加的职责可以撤销时。
③ 当无法通过继承来扩展一个类时(例如,因为类定义为 final)。
9.5 结构(Structure)
1
classDiagram
2
class Component {
3
<<abstract>>
4
+operation()
5
}
6
class ConcreteComponent {
7
+operation()
8
}
9
class Decorator {
10
+operation()
11
}
12
class ConcreteDecoratorA {
13
+operation()
14
}
15
class ConcreteDecoratorB {
16
+operation()
17
}
18
19
Component <|-- ConcreteComponent
20
Component <|-- Decorator
21
Decorator --o Component : component
22
Decorator <|-- ConcreteDecoratorA
23
Decorator <|-- ConcreteDecoratorB
① Component(组件):
▮▮▮▮ⓐ 定义一个对象接口,可以动态地添加职责。
② ConcreteComponent(具体组件):
▮▮▮▮ⓐ 定义一个对象,可以向其添加职责。
③ Decorator(装饰器):
▮▮▮▮ⓐ 维护一个指向 Component 类型对象的引用。
▮▮▮▮ⓑ 定义一个与 Component 接口一致的接口。
④ ConcreteDecorator(具体装饰器):
▮▮▮▮ⓐ 向组件添加职责。
9.6 参与者(Participants)
① Component (VisualComponent)
▮▮▮▮ⓐ 定义一个对象接口,可以动态地添加职责。
② ConcreteComponent (TextView)
▮▮▮▮ⓐ 定义一个对象,可以向其添加职责。
③ Decorator (Decorator)
▮▮▮▮ⓐ 维护一个指向 Component 类型对象的引用。
▮▮▮▮ⓑ 定义一个与 Component 接口一致的接口。
④ ConcreteDecorator (BorderDecorator, ScrollbarDecorator)
▮▮▮▮ⓐ 向组件添加职责。
9.7 协作(Collaborations)
① 装饰器将请求转发给它的组件,并且可能会在转发之前或之后执行一些额外的操作。
9.8 效果(Consequences)
装饰器模式有以下优点和缺点:
① 优点:
▮▮▮▮ⓐ 比静态继承更灵活。 可以动态地添加和删除职责。
▮▮▮▮ⓑ 避免了特征爆炸。 不需要为每种可能的组合创建一个子类。
▮▮▮▮ⓒ 符合“开闭原则”。 可以通过创建新的装饰器来添加新的职责,而无需修改现有的组件。
② 缺点:
▮▮▮▮ⓐ 可能会导致系统中出现许多小对象。
▮▮▮▮ⓑ 装饰器和组件之间的区别可能不太明显。
▮▮▮▮ⓒ 如果装饰器的层次很深,可能会导致代码难以理解和调试。
9.9 实现(Implementation)
以下是一个用 C++ 实现装饰器模式的示例,模拟了咖啡的加料:
1
#include <iostream>
2
#include <string>
3
4
// 抽象组件:咖啡
5
class Coffee {
6
public:
7
virtual ~Coffee() = default;
8
virtual std::string getDescription() const = 0;
9
virtual double cost() const = 0;
10
};
11
12
// 具体组件:普通咖啡
13
class SimpleCoffee : public Coffee {
14
public:
15
std::string getDescription() const override {
16
return "Simple Coffee";
17
}
18
double cost() const override {
19
return 1.0;
20
}
21
};
22
23
// 抽象装饰器
24
class CoffeeDecorator : public Coffee {
25
public:
26
CoffeeDecorator(Coffee* coffee) : coffee_(coffee) {}
27
virtual ~CoffeeDecorator() override = default;
28
std::string getDescription() const override {
29
return coffee_->getDescription();
30
}
31
double cost() const override {
32
return coffee_->cost();
33
}
34
35
protected:
36
Coffee* coffee_;
37
};
38
39
// 具体装饰器:牛奶
40
class MilkDecorator : public CoffeeDecorator {
41
public:
42
MilkDecorator(Coffee* coffee) : CoffeeDecorator(coffee) {}
43
std::string getDescription() const override {
44
return CoffeeDecorator::getDescription() + ", Milk";
45
}
46
double cost() const override {
47
return CoffeeDecorator::cost() + 0.5;
48
}
49
};
50
51
// 具体装饰器:糖
52
class SugarDecorator : public CoffeeDecorator {
53
public:
54
SugarDecorator(Coffee* coffee) : CoffeeDecorator(coffee) {}
55
std::string getDescription() const override {
56
return CoffeeDecorator::getDescription() + ", Sugar";
57
}
58
double cost() const override {
59
return CoffeeDecorator::cost() + 0.2;
60
}
61
};
62
63
int main() {
64
Coffee* coffee1 = new SimpleCoffee();
65
std::cout << "Coffee 1: " << coffee1->getDescription() << ", Cost: $" << coffee1->cost() << std::endl;
66
67
Coffee* coffee2 = new MilkDecorator(new SimpleCoffee());
68
std::cout << "Coffee 2: " << coffee2->getDescription() << ", Cost: $" << coffee2->cost() << std::endl;
69
70
Coffee* coffee3 = new SugarDecorator(new MilkDecorator(new SimpleCoffee()));
71
std::cout << "Coffee 3: " << coffee3->getDescription() << ", Cost: $" << coffee3->cost() << std::endl;
72
73
Coffee* coffee4 = new MilkDecorator(new SugarDecorator(new SimpleCoffee()));
74
std::cout << "Coffee 4: " << coffee4->getDescription() << ", Cost: $" << coffee4->cost() << std::endl;
75
76
delete coffee1;
77
delete coffee2;
78
delete coffee3;
79
delete coffee4;
80
81
return 0;
82
}
9.10 已知应用(Known Uses)
⚝ Java I/O 流(例如,BufferedInputStream
, FileInputStream
)。
⚝ GUI 工具包中的边框、滚动条等。
⚝ 责任链模式中的处理程序。
9.11 相关模式(Related Patterns)
① Adapter(适配器): 适配器模式改变对象的接口,而装饰器模式增强对象的功能,不改变接口。
② Composite(组合): 装饰器可以看作是只有一个组件的组合。
③ Strategy(策略): 装饰器模式允许动态地改变对象的职责,而策略模式允许动态地改变对象的行为。
10. Design Pattern 10: Facade(外观)
10.1 意图(Intent)
为子系统中的一组接口提供一个统一的接口。Facade 模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
10.2 别名(Also Known As)
⚝ None commonly known.
10.3 动机(Motivation)
复杂的子系统通常包含许多类。客户端如果直接与这些类交互,可能会非常复杂且容易出错。外观模式通过提供一个简单的接口来隐藏子系统的复杂性,使得客户端更容易使用子系统。
外观类包装了子系统中的一组类,并将客户端的请求转发给适当的子系统对象。这样,客户端只需要与外观类交互,而无需了解子系统的内部结构。
10.4 适用性(Applicability)
在以下情况下可以使用外观模式:
① 当你需要为一个复杂的子系统提供一个简单的接口时。
② 当客户程序与抽象类的多个子类之间存在很大的依赖性时。
③ 当你希望将一个子系统划分成为若干层次时,可以使用外观模式定义子系统中每个层次的入口点。
10.5 结构(Structure)
1
classDiagram
2
class Facade {
3
+operation1()
4
+operation2()
5
}
6
class SubsystemClass1 {
7
+operation1()
8
+operationA()
9
}
10
class SubsystemClass2 {
11
+operation2()
12
+operationB()
13
}
14
class SubsystemClass3 {
15
+operation3()
16
}
17
18
Facade --o SubsystemClass1
19
Facade --o SubsystemClass2
20
Facade --o SubsystemClass3
① Facade(外观):
▮▮▮▮ⓐ 知道哪些子系统类负责处理请求。
▮▮▮▮ⓑ 将客户的请求代理给适当的子系统对象。
② Subsystem classes(子系统类):
▮▮▮▮ⓐ 实现子系统的功能。
▮▮▮▮ⓑ 处理由 Facade 对象指派的任务。
▮▮▮▮ⓒ 对 Facade 没有一点了解;也就是说,它们不持有 Facade 的引用。
③ Client(客户):
▮▮▮▮ⓐ 通过 Facade 对象调用子系统。
10.6 参与者(Participants)
① Facade (Compiler)
▮▮▮▮ⓐ 知道哪些子系统类负责处理请求。
▮▮▮▮ⓑ 将客户的请求代理给适当的子系统对象。
② Subsystem classes (Scanner, Parser, CodeGenerator, Optimizer)
▮▮▮▮ⓐ 实现子系统的功能。
③ Client (Programmer)
▮▮▮▮ⓐ 通过 Facade 对象调用子系统。
10.7 协作(Collaborations)
① 客户端通过调用 Facade 对象上的方法来与子系统交互。
② Facade 对象知道哪些子系统对象负责处理请求,并将客户端的请求转发给这些对象。
③ 客户端不需要直接与子系统对象交互。
10.8 效果(Consequences)
外观模式有以下优点和缺点:
① 优点:
▮▮▮▮ⓐ 它对客户屏蔽了子系统组件,因而减少了客户处理的对象的数目并使得子系统使用起来更加容易。
▮▮▮▮ⓑ 它实现了客户程序与子系统对象之间的松耦合。 子系统的变化不会影响到客户。
▮▮▮▮ⓒ 它允许你降低子系统内部各成分之间的依赖性。
② 缺点:
▮▮▮▮ⓐ 可能使得外观对象成为一个“上帝对象”,它知道太多关于子系统的信息。
▮▮▮▮ⓑ 不符合“开闭原则”,如果需要添加新的子系统功能,可能需要修改外观类。
10.9 实现(Implementation)
以下是一个用 C++ 实现外观模式的示例,模拟了一个简化的计算机启动过程:
1
#include <iostream>
2
#include <string>
3
4
// 子系统类:CPU
5
class CPU {
6
public:
7
void start() {
8
std::cout << "CPU: Starting..." << std::endl;
9
}
10
void execute() {
11
std::cout << "CPU: Executing instructions..." << std::endl;
12
}
13
};
14
15
// 子系统类:内存
16
class Memory {
17
public:
18
void load(long address, const std::string& data) {
19
std::cout << "Memory: Loading data '" << data << "' at address " << address << std::endl;
20
}
21
};
22
23
// 子系统类:硬盘
24
class HardDrive {
25
public:
26
std::string read(long lba, int size) {
27
std::cout << "HardDrive: Reading " << size << " bytes from LBA " << lba << std::endl;
28
return "Operating System";
29
}
30
};
31
32
// 外观类:计算机
33
class ComputerFacade {
34
public:
35
ComputerFacade() : cpu_(new CPU()), memory_(new Memory()), hardDrive_(new HardDrive()) {}
36
~ComputerFacade() {
37
delete cpu_;
38
delete memory_;
39
delete hardDrive_;
40
}
41
42
void startComputer() {
43
cpu_->start();
44
std::string os = hardDrive_->read(0, 1024);
45
memory_->load(0x1000, os);
46
cpu_->execute();
47
std::cout << "Computer: Booted successfully!" << std::endl;
48
}
49
50
private:
51
CPU* cpu_;
52
Memory* memory_;
53
HardDrive* hardDrive_;
54
};
55
56
int main() {
57
ComputerFacade computer;
58
computer.startComputer();
59
60
return 0;
61
}
10.10 已知应用(Known Uses)
⚝ 编译器前端
⚝ 操作系统 API
⚝ 中间件
10.11 相关模式(Related Patterns)
① Abstract Factory(抽象工厂): 外观可以与抽象工厂一起使用,以提供一个接口来创建子系统对象。
② Mediator(中介者): 中介者模式与外观模式类似,但中介者定义了对象之间的通信和控制逻辑,而外观模式只是提供了一个简化的接口。
③ Adapter(适配器): 适配器模式用于改变单个类的接口,而外观模式为多个类的接口提供一个统一的接口。
11. Design Pattern 11: Flyweight(享元)
11.1 意图(Intent)
运用共享技术有效地支持大量细粒度的对象。
11.2 别名(Also Known As)
⚝ None commonly known.
11.3 动机(Motivation)
在某些应用中,可能需要创建大量的细粒度对象。如果每个对象都包含自己的状态,那么可能会消耗大量的内存。享元模式通过将对象的状态分为内部状态(intrinsic state)和外部状态(extrinsic state)来解决这个问题。内部状态是对象之间共享的,而外部状态是每个对象特有的,并由客户端在运行时提供。
享元模式维护一个享元池(flyweight pool),用于存储已经创建的享元对象。当客户端请求一个享元对象时,享元工厂会首先检查享元池中是否已经存在具有相同内部状态的对象。如果存在,则直接返回池中的对象;否则,创建一个新的享元对象并将其添加到池中。客户端在使用享元对象时,需要提供外部状态。
11.4 适用性(Applicability)
在以下情况下可以使用享元模式:
① 一个应用使用了大量的对象。
② 完全由于使用大量的细粒度对象而导致了巨大的存储开销。
③ 对象的大多数状态都可以变为外部状态。
④ 如果剔除对象的外部状态,那么可以用相对较少的共享对象取代很多组内部状态。
⑤ 当应用不依赖于对象标识时。由于 Flyweight 对象可以被共享,对于概念上明显不同的对象,标识测试将返回真值。
11.5 结构(Structure)
1
classDiagram
2
class Flyweight {
3
<<abstract>>
4
+operation(ExtrinsicState)
5
}
6
class ConcreteFlyweight {
7
+operation(ExtrinsicState)
8
}
9
class UnsharedConcreteFlyweight {
10
+operation(ExtrinsicState)
11
}
12
class FlyweightFactory {
13
+getFlyweight(IntrinsicState) Flyweight*
14
}
15
class Client {
16
+operation()
17
}
18
19
Flyweight <|-- ConcreteFlyweight
20
Flyweight <|-- UnsharedConcreteFlyweight
21
FlyweightFactory ..> Flyweight
22
Client ..> FlyweightFactory
23
Client ..> ExtrinsicState
① Flyweight(享元):
▮▮▮▮ⓐ 声明一个接口,通过这个接口 flyweight 可以接受并作用于外部状态。
② ConcreteFlyweight(具体享元):
▮▮▮▮ⓐ 实现 Flyweight 接口,并且如果需要的话,为内部状态增加存储空间。一个 ConcreteFlyweight 对象是可共享的。它存储的状态必须是内部的;也就是说,它必须独立于 ConcreteFlyweight 对象的外部环境。
③ UnsharedConcreteFlyweight(非共享具体享元):
▮▮▮▮ⓐ 并非所有的 Flyweight 子类都需要被共享。Flyweight 接口使得共享成为可能,但它并不强制共享。通常,UnsharedConcreteFlyweight 对象作为 ConcreteFlyweight 对象组合的子节点。
④ FlyweightFactory(享元工厂):
▮▮▮▮ⓐ 创建并管理 Flyweight 对象。
▮▮▮▮ⓑ 确保合理地共享 flyweight;当用户请求一个 flyweight 时,FlyweightFactory 对象提供一个已存在的实例或者创建一个实例(如果不存在的话)。
⑤ Client(客户):
▮▮▮▮ⓐ 维护一个对 flyweight 的引用。
▮▮▮▮ⓑ 计算或存储 flyweight 的外部状态。
11.6 参与者(Participants)
① Flyweight (Character)
▮▮▮▮ⓐ 声明一个接口,通过这个接口 flyweight 可以接受并作用于外部状态。
② ConcreteFlyweight (Character 'A', 'B', 'Z', ...)
▮▮▮▮ⓐ 实现 Flyweight 接口,并且如果需要的话,为内部状态增加存储空间。
③ UnsharedConcreteFlyweight (Row, Column)
▮▮▮▮ⓐ 并非所有的 Flyweight 子类都需要被共享。
④ FlyweightFactory (CharacterFactory)
▮▮▮▮ⓐ 创建并管理 Flyweight 对象。
⑤ Client (Document)
▮▮▮▮ⓐ 维护一个对 flyweight 的引用。
▮▮▮▮ⓑ 计算或存储 flyweight 的外部状态。
11.7 协作(Collaborations)
① 客户端不直接实例化 Flyweight 对象,而是从 FlyweightFactory 请求。
② FlyweightFactory 将内部状态作为键提供给 Flyweight 池。当请求时,工厂返回一个已经存在的 flyweight 或者创建一个新的 flyweight(如果不存在)。
③ 客户端提供在操作中使用所需的外部状态。
11.8 效果(Consequences)
享元模式有以下优点和缺点:
① 优点:
▮▮▮▮ⓐ 可以显著地减少内存占用,特别是当存在大量重复的细粒度对象时。
▮▮▮▮ⓑ 提高了性能,因为减少了对象的创建和销毁。
② 缺点:
▮▮▮▮ⓐ 增加了系统的复杂性。 需要分离内部状态和外部状态,并且需要管理享元池。
▮▮▮▮ⓑ 可能会增加获取享元对象的时间。 需要在享元池中查找对象。
11.9 实现(Implementation)
以下是一个用 C++ 实现享元模式的示例,模拟了文本编辑器中的字符:
1
#include <iostream>
2
#include <string>
3
#include <map>
4
5
// 享元接口:字符
6
class Character {
7
public:
8
virtual ~Character() = default;
9
virtual void display(int x, int y) = 0;
10
};
11
12
// 具体享元:具体的字符
13
class ConcreteCharacter : public Character {
14
public:
15
ConcreteCharacter(char c) : character_(c) {}
16
void display(int x, int y) override {
17
std::cout << "Displaying character '" << character_ << "' at (" << x << ", " << y << ")" << std::endl;
18
}
19
20
private:
21
char character_;
22
};
23
24
// 享元工厂
25
class CharacterFactory {
26
public:
27
static CharacterFactory& getInstance() {
28
static CharacterFactory instance;
29
return instance;
30
}
31
32
Character* getCharacter(char key) {
33
if (pool_.find(key) == pool_.end()) {
34
pool_[key] = new ConcreteCharacter(key);
35
}
36
return pool_[key];
37
}
38
39
private:
40
CharacterFactory() {}
41
CharacterFactory(const CharacterFactory&) = delete;
42
CharacterFactory& operator=(const CharacterFactory&) = delete;
43
44
std::map<char, Character*> pool_;
45
};
46
47
int main() {
48
CharacterFactory& factory = CharacterFactory::getInstance();
49
50
// 外部状态
51
int x[] = {10, 20, 30, 40, 50};
52
int y[] = {10, 20, 30, 40, 50};
53
char text[] = {'H', 'e', 'l', 'l', 'o'};
54
55
for (int i = 0; i < 5; ++i) {
56
Character* character = factory.getCharacter(text[i]);
57
character->display(x[i], y[i]);
58
}
59
60
// 注意:享元对象通常由工厂管理,不需要客户端显式删除。
61
// 在这个简单的例子中,工厂在程序结束时会自动清理。
62
63
return 0;
64
}
11.10 已知应用(Known Uses)
⚝ 文本编辑器中共享的字符对象。
⚝ 游戏开发中共享的纹理或模型。
⚝ 文档处理应用中共享的格式化信息。
11.11 相关模式(Related Patterns)
① Strategy(策略): 享元对象通常作为策略对象使用。
② Composite(组合): 享元模式通常与组合模式结合使用,以表示共享的叶节点对象。
12. Design Pattern 12: Proxy(代理)
12.1 意图(Intent)
为其他对象提供一种代理以控制对这个对象的访问。
12.2 别名(Also Known As)
⚝ Surrogate
12.3 动机(Motivation)
在某些情况下,直接访问一个对象可能会很昂贵、复杂或者需要一些额外的控制。例如,访问一个远程对象、创建一个大型对象或者需要进行权限检查。代理模式通过创建一个代理对象来代表原始对象,并在必要时才将请求转发给原始对象,从而解决了这个问题。
代理对象与原始对象具有相同的接口,因此客户端可以像使用原始对象一样使用代理对象。代理对象可以在转发请求之前或之后执行一些额外的操作,例如延迟加载、缓存结果、记录日志或者进行安全检查。
12.4 适用性(Applicability)
在以下情况下可以使用代理模式:
① 远程代理(Remote Proxy): 为一个不同地址空间中的对象提供一个本地代表。
② 虚代理(Virtual Proxy): 在需要时才创建开销大的对象。
③ 保护代理(Protection Proxy): 控制对原始对象的访问。
④ 智能指引(Smart Reference): 当指向原始对象的引用被访问时,执行一些额外的操作,例如记录对象的访问次数或者在对象不再被使用时释放资源。
12.5 结构(Structure)
1
classDiagram
2
class Subject {
3
<<interface>>
4
+request()
5
}
6
class RealSubject {
7
+request()
8
}
9
class Proxy {
10
+request()
11
}
12
13
Subject <|-- RealSubject
14
Subject <|-- Proxy
15
Proxy --o RealSubject : realSubject
① Subject(主题):
▮▮▮▮ⓐ 定义 RealSubject 和 Proxy 的共同接口,这样在任何使用 RealSubject 的地方都可以使用 Proxy。
② RealSubject(真实主题):
▮▮▮▮ⓐ 定义了 Proxy 所代表的真实对象。
③ Proxy(代理):
▮▮▮▮ⓐ 持有一个对 RealSubject 的引用(如果需要)。
▮▮▮▮ⓑ 实现 Subject 接口。
▮▮▮▮ⓒ 控制对 RealSubject 的访问,并且可能负责创建和删除 RealSubject。
▮▮▮▮ⓓ 在转发请求给 RealSubject 之前或之后,可能会添加额外的功能。
12.6 参与者(Participants)
① Subject (Image)
▮▮▮▮ⓐ 定义 RealSubject 和 Proxy 的共同接口。
② RealSubject (Bitmap)
▮▮▮▮ⓐ 定义了 Proxy 所代表的真实对象。
③ Proxy (ImageProxy)
▮▮▮▮ⓐ 持有一个对 RealSubject 的引用。
▮▮▮▮ⓑ 实现 Subject 接口。
▮▮▮▮ⓒ 控制对 RealSubject 的访问,并且可能负责创建和删除 RealSubject。
12.7 协作(Collaborations)
① Proxy 通常会引用一个 RealSubject。如果需要,Proxy 会创建一个 RealSubject 对象并把工作委托给它。
12.8 效果(Consequences)
代理模式有以下优点和缺点:
① 优点:
▮▮▮▮ⓐ 远程代理: 隐藏了远程对象的细节。
▮▮▮▮ⓑ 虚代理: 可以延迟对象的创建,直到真正需要时才创建,从而提高了程序的性能。
▮▮▮▮ⓒ 保护代理: 可以控制对原始对象的访问权限。
▮▮▮▮ⓓ 智能指引: 可以在访问对象时执行额外的操作。
② 缺点:
▮▮▮▮ⓐ 可能会增加系统的复杂性,因为需要引入额外的代理类。
▮▮▮▮ⓑ 在某些情况下,可能会导致请求的处理速度变慢,因为需要经过代理对象的中转。
12.9 实现(Implementation)
以下是一个用 C++ 实现虚代理模式的示例,模拟了延迟加载大型图片:
1
#include <iostream>
2
#include <string>
3
4
// 主题接口:图片
5
class Image {
6
public:
7
virtual ~Image() = default;
8
virtual void display() = 0;
9
};
10
11
// 真实主题:大型图片
12
class RealImage : public Image {
13
public:
14
RealImage(const std::string& filename) : filename_(filename) {
15
loadFromDisk(filename_);
16
}
17
void display() override {
18
std::cout << "Displaying image: " << filename_ << std::endl;
19
}
20
21
private:
22
void loadFromDisk(const std::string& filename) {
23
std::cout << "Loading image from disk: " << filename << std::endl;
24
// 模拟耗时的加载过程
25
}
26
std::string filename_;
27
};
28
29
// 代理:图片代理
30
class ImageProxy : public Image {
31
public:
32
ImageProxy(const std::string& filename) : filename_(filename), realImage_(nullptr) {}
33
~ImageProxy() override {
34
delete realImage_;
35
}
36
void display() override {
37
if (realImage_ == nullptr) {
38
realImage_ = new RealImage(filename_);
39
}
40
realImage_->display();
41
}
42
43
private:
44
std::string filename_;
45
RealImage* realImage_;
46
};
47
48
int main() {
49
// 图片代理,此时 RealImage 尚未加载
50
Image* image1 = new ImageProxy("large_image.jpg");
51
std::cout << "ImageProxy created." << std::endl;
52
53
// 第一次调用 display 时,RealImage 才会被加载
54
image1->display();
55
std::cout << "First display done." << std::endl;
56
57
// 第二次调用 display 时,直接使用已经加载的 RealImage
58
image1->display();
59
60
delete image1;
61
62
return 0;
63
}
12.10 已知应用(Known Uses)
⚝ 远程方法调用(RMI)中的存根(Stub)。
⚝ 延迟加载大型对象。
⚝ 访问控制列表(ACL)。
⚝ 智能指针。
12.11 相关模式(Related Patterns)
① Adapter(适配器): 适配器模式改变对象的接口,而代理模式控制对对象的访问,不改变接口。
② Decorator(装饰器): 装饰器模式增强对象的功能,而代理模式控制对对象的访问。
③ Facade(外观): 外观模式为子系统提供一个统一的接口,而代理模式通常只代理一个对象。
13. Design Pattern 13: Chain of Responsibility(责任链)
13.1 意图(Intent)
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。
13.2 别名(Also Known As)
⚝ None commonly known.
13.3 动机(Motivation)
假设你需要处理不同类型的请求,并且处理逻辑取决于请求的类型。一种方法是在一个大的条件语句中处理所有类型的请求,但这会导致代码难以维护和扩展。
责任链模式通过将请求的处理逻辑分布到一系列的处理对象中来解决这个问题。每个处理对象负责处理特定类型的请求,或者将请求传递给链中的下一个处理对象。客户端只需要将请求发送给链中的第一个处理对象,而无需知道哪个对象最终会处理它。
13.4 适用性(Applicability)
在以下情况下可以使用责任链模式:
① 有多个对象可以处理一个请求,但事先不知道由哪个对象处理该请求。
② 你想在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
③ 可处理一个请求的对象集合应被动态指定。
13.5 结构(Structure)
1
classDiagram
2
class Handler {
3
<<abstract>>
4
+setSuccessor(Handler*)
5
+handleRequest(Request)
6
}
7
class ConcreteHandler1 {
8
+handleRequest(Request)
9
}
10
class ConcreteHandler2 {
11
+handleRequest(Request)
12
}
13
class Client {
14
+sendRequest(Request)
15
}
16
17
Handler --o Handler : successor
18
Handler <|-- ConcreteHandler1
19
Handler <|-- ConcreteHandler2
20
Client ..> Handler
21
Client ..> Request
① Handler(处理者):
▮▮▮▮ⓐ 定义一个处理请求的接口。
▮▮▮▮ⓑ (可选)实现后继链。
② ConcreteHandler(具体处理者):
▮▮▮▮ⓐ 处理它所负责的请求。
▮▮▮▮ⓑ 可以访问后继者。
▮▮▮▮ⓒ 如果 ConcreteHandler 可以处理该请求,就处理它;否则就将该请求转发给它的后继者。
③ Client(客户):
▮▮▮▮ⓐ 向链中的第一个处理者对象提交请求。
13.6 参与者(Participants)
① Handler (HelpHandler)
▮▮▮▮ⓐ 定义一个处理请求的接口。
② ConcreteHandler (TopicHandler, FileHandler)
▮▮▮▮ⓐ 处理它所负责的请求。
③ Client
▮▮▮▮ⓐ 向链中的第一个处理者对象提交请求。
13.7 协作(Collaborations)
① 当客户提交一个请求时,该请求沿着链传递直至有一个 ConcreteHandler 对象处理它。
② 客户并不知道链中的哪个 ConcreteHandler 对象最终处理了该请求,以及链的结构。
③ 结果是各 ConcreteHandler 对象之间松散耦合。一个 ConcreteHandler 可以决定是否处理该请求,或者是否将该请求传递给链中的后继者。
④ 可以通过简单地重新组织链来动态地增加或修改处理职责。
13.8 效果(Consequences)
责任链模式有以下优点和缺点:
① 优点:
▮▮▮▮ⓐ 降低了耦合度。 请求的发送者和接收者之间没有明确的耦合关系。
▮▮▮▮ⓑ 增加了对象的灵活性。 可以动态地增加或修改处理请求的职责。
▮▮▮▮ⓒ 请求处理对象可以被复用。
② 缺点:
▮▮▮▮ⓐ 请求不一定会被处理。 如果链的末端没有合适的处理者,请求可能会被丢弃。
▮▮▮▮ⓑ 可能会难以观察运行时发生的处理过程。
13.9 实现(Implementation)
以下是一个用 C++ 实现责任链模式的示例,模拟了不同级别的错误日志记录器:
1
#include <iostream>
2
#include <string>
3
4
enum class LogLevel {
5
INFO,
6
DEBUG,
7
ERROR
8
};
9
10
// 抽象处理者:日志记录器
11
class Logger {
12
public:
13
Logger(LogLevel level) : level_(level), nextLogger_(nullptr) {}
14
virtual ~Logger() = default;
15
16
void setNext(Logger* nextLogger) {
17
nextLogger_ = nextLogger;
18
}
19
20
void logMessage(LogLevel level, const std::string& message) {
21
if (level >= level_) {
22
writeMessage(message);
23
}
24
if (nextLogger_ != nullptr && level > level_) {
25
nextLogger_->logMessage(level, message);
26
}
27
}
28
29
protected:
30
virtual void writeMessage(const std::string& message) = 0;
31
LogLevel level_;
32
Logger* nextLogger_;
33
};
34
35
// 具体处理者:控制台日志记录器
36
class ConsoleLogger : public Logger {
37
public:
38
ConsoleLogger(LogLevel level) : Logger(level) {}
39
protected:
40
void writeMessage(const std::string& message) override {
41
std::cout << "Console::Logger: " << message << std::endl;
42
}
43
};
44
45
// 具体处理者:文件日志记录器
46
class FileLogger : public Logger {
47
public:
48
FileLogger(LogLevel level, const std::string& filename) : Logger(level), filename_(filename) {}
49
protected:
50
void writeMessage(const std::string& message) override {
51
std::cout << "File::Logger: Writing message to " << filename_ << ": " << message << std::endl;
52
}
53
private:
54
std::string filename_;
55
};
56
57
// 具体处理者:错误日志记录器
58
class ErrorLogger : public Logger {
59
public:
60
ErrorLogger(LogLevel level) : Logger(level) {}
61
protected:
62
void writeMessage(const std::string& message) override {
63
std::cerr << "Error::Logger: " << message << std::endl;
64
}
65
};
66
67
int main() {
68
// 创建责任链
69
Logger* consoleLogger = new ConsoleLogger(LogLevel::INFO);
70
Logger* fileLogger = new FileLogger(LogLevel::DEBUG, "log.txt");
71
Logger* errorLogger = new ErrorLogger(LogLevel::ERROR);
72
73
consoleLogger->setNext(fileLogger);
74
fileLogger->setNext(errorLogger);
75
76
// 客户端发送不同级别的日志消息
77
consoleLogger->logMessage(LogLevel::INFO, "This is an info message.");
78
consoleLogger->logMessage(LogLevel::DEBUG, "This is a debug message.");
79
consoleLogger->logMessage(LogLevel::ERROR, "This is an error message.");
80
81
delete consoleLogger;
82
delete fileLogger;
83
delete errorLogger;
84
85
return 0;
86
}
13.10 已知应用(Known Uses)
⚝ GUI 中的事件处理。
⚝ 异常处理。
⚝ 过滤器链。
⚝ 授权和身份验证。
13.11 相关模式(Related Patterns)
① Command(命令): 责任链中的处理者可以看作是命令对象。
② Decorator(装饰器): 装饰器模式可以动态地添加职责,而责任链模式则将职责分布到一系列对象中。
③ Mediator(中介者): 中介者模式用于集中处理对象之间的交互,而责任链模式用于处理请求的传递。
14. Design Pattern 14: Command(命令)
14.1 意图(Intent)
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
14.2 别名(Also Known As)
⚝ Action, Transaction
14.3 动机(Motivation)
假设你需要实现一个菜单系统,用户可以点击菜单项来执行不同的操作。一种方法是将每个菜单项的操作直接绑定到菜单项对象上,但这会导致菜单项对象过于复杂,并且难以扩展。
命令模式通过将每个操作封装在一个命令对象中来解决这个问题。命令对象包含执行操作所需的所有信息,例如接收者对象和要调用的方法。菜单项对象只需要持有一个命令对象的引用,并在被点击时调用命令对象的执行方法。这样,就可以将请求的发送者(菜单项)和接收者(实际执行操作的对象)解耦,并且可以方便地添加新的操作。
14.4 适用性(Applicability)
在以下情况下可以使用命令模式:
① 当你需要参数化对象以执行的操作时。
② 当你需要指定、排列和执行请求时。
③ 当你需要支持撤销操作时。
④ 当你需要支持日志记录操作时。
⑤ 当你需要支持事务操作时。
14.5 结构(Structure)
1
classDiagram
2
class Command {
3
<<abstract>>
4
+execute()
5
}
6
class ConcreteCommand {
7
+execute()
8
}
9
class Receiver {
10
+action()
11
}
12
class Invoker {
13
+setCommand(Command*)
14
+executeCommand()
15
}
16
class Client {
17
+createCommand() Command*
18
}
19
20
Command <|-- ConcreteCommand
21
ConcreteCommand --o Receiver : receiver
22
Invoker --o Command : command
23
Client ..> Invoker
24
Client ..> ConcreteCommand
① Command(命令):
▮▮▮▮ⓐ 声明执行操作的接口。
② ConcreteCommand(具体命令):
▮▮▮▮ⓐ 将一个接收者对象绑定于一个动作。
▮▮▮▮ⓑ 调用接收者相应的操作,以实现执行。
③ Client(客户):
▮▮▮▮ⓐ 创建一个 ConcreteCommand 对象并设置其接收者。
④ Invoker(调用者):
▮▮▮▮ⓐ 要求该命令执行这个请求。
⑤ Receiver(接收者):
▮▮▮▮ⓐ 知道如何实施与执行一个请求相关的操作。任何类的实例都可能充当一个接收者。
14.6 参与者(Participants)
① Command (Command)
▮▮▮▮ⓐ 声明执行操作的接口。
② ConcreteCommand (OpenCommand, PasteCommand)
▮▮▮▮ⓐ 将一个接收者对象绑定于一个动作。
③ Client (Application)
▮▮▮▮ⓐ 创建一个 ConcreteCommand 对象并设置其接收者。
④ Invoker (MenuItem)
▮▮▮▮ⓐ 要求该命令执行这个请求。
⑤ Receiver (Document)
▮▮▮▮ⓐ 知道如何实施与执行一个请求相关的操作。
14.7 协作(Collaborations)
① 客户创建一个 ConcreteCommand 对象并指定它的接收者。
② Invoker 对象存储该 ConcreteCommand 对象。
③ Invoker 通过调用该命令的 Execute
操作来发出一个请求。当命令被执行时,它通过调用它的接收者的相关操作来实施请求。
14.8 效果(Consequences)
命令模式有以下优点和缺点:
① 优点:
▮▮▮▮ⓐ 命令模式将调用操作的对象与知道如何实现该操作的对象解耦。
▮▮▮▮ⓑ 命令是头等对象。 它们可以像其他对象一样被操纵和扩展。
▮▮▮▮ⓒ 你可以组合命令来形成复合命令。
▮▮▮▮ⓓ 很容易实现对请求的撤销和重做。
▮▮▮▮ⓔ 可以很容易地实现事务。
② 缺点:
▮▮▮▮ⓐ 可能会导致系统中出现大量的命令类。
14.9 实现(Implementation)
以下是一个用 C++ 实现命令模式的示例,模拟了简单的文本编辑器操作:
1
#include <iostream>
2
#include <string>
3
#include <vector>
4
5
// 接收者:文本编辑器
6
class TextEditor {
7
public:
8
void open() {
9
std::cout << "TextEditor: Opening document." << std::endl;
10
}
11
void save() {
12
std::cout << "TextEditor: Saving document." << std::endl;
13
}
14
};
15
16
// 抽象命令
17
class Command {
18
public:
19
virtual ~Command() = default;
20
virtual void execute() = 0;
21
};
22
23
// 具体命令:打开命令
24
class OpenCommand : public Command {
25
public:
26
OpenCommand(TextEditor* editor) : editor_(editor) {}
27
void execute() override {
28
editor_->open();
29
}
30
private:
31
TextEditor* editor_;
32
};
33
34
// 具体命令:保存命令
35
class SaveCommand : public Command {
36
public:
37
SaveCommand(TextEditor* editor) : editor_(editor) {}
38
void execute() override {
39
editor_->save();
40
}
41
private:
42
TextEditor* editor_;
43
};
44
45
// 调用者:菜单项
46
class MenuItem {
47
public:
48
void setCommand(Command* command) {
49
command_ = command;
50
}
51
void click() {
52
if (command_ != nullptr) {
53
command_->execute();
54
}
55
}
56
private:
57
Command* command_;
58
};
59
60
int main() {
61
TextEditor* editor = new TextEditor();
62
OpenCommand* openCommand = new OpenCommand(editor);
63
SaveCommand* saveCommand = new SaveCommand(editor);
64
65
MenuItem openMenuItem;
66
openMenuItem.setCommand(openCommand);
67
68
MenuItem saveMenuItem;
69
saveMenuItem.setCommand(saveCommand);
70
71
openMenuItem.click();
72
saveMenuItem.click();
73
74
delete editor;
75
delete openCommand;
76
delete saveCommand;
77
78
return 0;
79
}
14.10 已知应用(Known Uses)
⚝ GUI 中的菜单项和按钮。
⚝ 事务处理。
⚝ 宏录制。
⚝ 撤销/重做功能。
14.11 相关模式(Related Patterns)
① Memento(备忘录): 命令模式可以与备忘录模式一起使用,以实现撤销和重做功能。
② Composite(组合): 可以使用组合模式来实现复合命令。
③ Strategy(策略): 命令对象可以看作是一种策略。
15. Design Pattern 15: Interpreter(解释器)
15.1 意图(Intent)
给定一个语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。
15.2 别名(Also Known As)
⚝ None commonly known.
15.3 动机(Motivation)
如果一种特定类型的问题发生得足够频繁,那么可能值得将该问题的各个实例表述为一个简单语言中的句子。接着就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。
例如,搜索匹配某个模式的字符串。该模式可以用一个正则表达式来描述。解释这个模式的是一个正则表达式引擎。解释器模式描述了如何在给定一个语言时,如何定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。
15.4 适用性(Applicability)
在以下情况下可以使用解释器模式:
① 当存在一种语言需要解释执行,并且你可将该语言中的句子表示为一个抽象语法树时。
② 当文法较为简单时,对于复杂的文法,文法的层次会变得很庞大,难以管理。
15.5 结构(Structure)
1
classDiagram
2
class AbstractExpression {
3
<<abstract>>
4
+interpret(Context)
5
}
6
class TerminalExpression {
7
+interpret(Context)
8
}
9
class NonterminalExpression {
10
+interpret(Context)
11
}
12
class Context {
13
// ...
14
}
15
class Client {
16
// ...
17
}
18
19
AbstractExpression <|-- TerminalExpression
20
AbstractExpression <|-- NonterminalExpression
21
NonterminalExpression --o AbstractExpression : expression
22
Client ..> AbstractExpression
23
Client ..> Context
① AbstractExpression(抽象表达式):
▮▮▮▮ⓐ 声明一个抽象的 Interpret
操作,这个接口被所有的终结符表达式和非终结符表达式实现。
② TerminalExpression(终结符表达式):
▮▮▮▮ⓐ 实现与文法中的终结符相关的解释操作。
▮▮▮▮ⓑ 一个句子中的每个终结符需要该类的一个实例。
③ NonterminalExpression(非终结符表达式):
▮▮▮▮ⓐ 文法中的每一条规则 R ::= R1R2 ... Rn 都需要一个具体的非终结符表达式类。
▮▮▮▮ⓑ 这些类中的实例变量用来存放解释 R1, R2, ... Rn 的实例。
▮▮▮▮ⓒ 实现 Interpret
操作,通常要递归地调用组成非终结符的各个符号对应的解释操作。
④ Context(环境):
▮▮▮▮ⓐ 包含解释器之外的一些全局信息。
⑤ Client(客户):
▮▮▮▮ⓐ 构建(或被给定)表示该特定句子的抽象语法树。这个抽象语法树由 NonterminalExpression 和 TerminalExpression 的实例装配而成。
▮▮▮▮ⓑ 调用解释操作。
15.6 参与者(Participants)
① AbstractExpression (RegularExpression)
▮▮▮▮ⓐ 声明一个抽象的 Interpret
操作。
② TerminalExpression (LiteralExpression)
▮▮▮▮ⓐ 实现与文法中的终结符相关的解释操作。
③ NonterminalExpression (AndExpression, OrExpression, NotExpression)
▮▮▮▮ⓐ 文法中的每一条规则都需要一个具体的非终结符表达式类。
④ Context
▮▮▮▮ⓐ 包含解释器之外的一些全局信息。
⑤ Client
▮▮▮▮ⓐ 构建表示句子的抽象语法树并调用解释操作。
15.7 协作(Collaborations)
① 客户构建一个表示句子的抽象语法树。
② 客户调用根表达式的 Interpret
操作。
③ 每一个非终结符表达式节点接着调用其子节点的 Interpret
操作。
④ 解释操作沿着这棵树递归地进行,当到达一个终结符表达式节点时,解释操作停止。
15.8 效果(Consequences)
解释器模式有以下优点和缺点:
① 优点:
▮▮▮▮ⓐ 易于改变和扩展文法。 因为该模式使用类来表示文法规则,你可以使用继承来改变或扩展文法。
▮▮▮▮ⓑ 易于实现文法。 文法规则中定义的每一个产生式都对应一个类。
② 缺点:
▮▮▮▮ⓐ 对于复杂的文法,类层次会变得很庞大。
▮▮▮▮ⓑ 对于复杂的文法,解释器的实现可能会变得很复杂。
15.9 实现(Implementation)
以下是一个用 C++ 实现解释器模式的示例,模拟了简单的布尔表达式解释器:
1
#include <iostream>
2
#include <string>
3
#include <map>
4
5
// 环境类
6
class Context {
7
public:
8
void assign(const std::string& variable, bool value) {
9
values_[variable] = value;
10
}
11
bool lookup(const std::string& variable) const {
12
auto it = values_.find(variable);
13
if (it != values_.end()) {
14
return it->second;
15
}
16
return false; // 默认返回 false
17
}
18
private:
19
std::map<std::string, bool> values_;
20
};
21
22
// 抽象表达式
23
class BooleanExpression {
24
public:
25
virtual ~BooleanExpression() = default;
26
virtual bool evaluate(const Context&) const = 0;
27
};
28
29
// 终结符表达式:变量
30
class VariableExpression : public BooleanExpression {
31
public:
32
VariableExpression(const std::string& name) : name_(name) {}
33
bool evaluate(const Context& context) const override {
34
return context.lookup(name_);
35
}
36
private:
37
std::string name_;
38
};
39
40
// 非终结符表达式:Or
41
class OrExpression : public BooleanExpression {
42
public:
43
OrExpression(BooleanExpression* left, BooleanExpression* right) : left_(left), right_(right) {}
44
~OrExpression() override {
45
delete left_;
46
delete right_;
47
}
48
bool evaluate(const Context& context) const override {
49
return left_->evaluate(context) || right_->evaluate(context);
50
}
51
private:
52
BooleanExpression* left_;
53
BooleanExpression* right_;
54
};
55
56
// 非终结符表达式:And
57
class AndExpression : public BooleanExpression {
58
public:
59
AndExpression(BooleanExpression* left, BooleanExpression* right) : left_(left), right_(right) {}
60
~AndExpression() override {
61
delete left_;
62
delete right_;
63
}
64
bool evaluate(const Context& context) const override {
65
return left_->evaluate(context) && right_->evaluate(context);
66
}
67
private:
68
BooleanExpression* left_;
69
BooleanExpression* right_;
70
};
71
72
int main() {
73
Context context;
74
context.assign("A", true);
75
context.assign("B", false);
76
77
// 表达式:(A and B) or A
78
BooleanExpression* a = new VariableExpression("A");
79
BooleanExpression* b = new VariableExpression("B");
80
BooleanExpression* andExp = new AndExpression(a, b);
81
BooleanExpression* orExp = new OrExpression(andExp, a);
82
83
bool result = orExp->evaluate(context);
84
std::cout << "(A and B) or A = " << std::boolalpha << result << std::endl;
85
86
delete orExp; // 也会删除 andExp, a, b
87
88
return 0;
89
}
15.10 已知应用(Known Uses)
⚝ SQL 解析器。
⚝ 正则表达式引擎。
⚝ 数学表达式求值器。
15.11 相关模式(Related Patterns)
① Composite(组合): 解释器模式通常使用组合模式来表示抽象语法树。
② Strategy(策略): 可以使用策略模式来定义不同的解释策略。
③ Template Method(模板方法): 可以使用模板方法来定义解释的通用算法,并将具体的解释步骤留给子类实现.
16. Design Pattern 16: Iterator(迭代器)
16.1 意图(Intent)
提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。
16.2 别名(Also Known As)
⚝ Cursor
16.3 动机(Motivation)
假设你有一个集合对象(例如,列表、数组、树),你需要遍历这个集合中的所有元素。一种方法是在集合对象中直接实现遍历逻辑,但这会导致集合对象过于复杂,并且暴露了集合对象的内部表示。
迭代器模式通过将遍历逻辑封装在一个独立的迭代器对象中来解决这个问题。迭代器对象知道如何访问集合对象中的元素,并提供了一个统一的接口来遍历集合。这样,集合对象只需要关注自身的存储和管理,而遍历的责任则交给迭代器。
16.4 适用性(Applicability)
在以下情况下可以使用迭代器模式:
① 当你需要访问一个聚合对象的内容,而又不暴露其内部表示时。
② 当你需要支持对聚合对象的多种遍历方式时。
③ 当你需要为聚合对象提供一个统一的遍历接口时。
16.5 结构(Structure)
1
classDiagram
2
class Iterator {
3
<<abstract>>
4
+first()
5
+next()
6
+isDone() : bool
7
+currentItem()
8
}
9
class ConcreteIterator {
10
+first()
11
+next()
12
+isDone() : bool
13
+currentItem()
14
}
15
class Aggregate {
16
<<abstract>>
17
+createIterator() : Iterator*
18
}
19
class ConcreteAggregate {
20
+createIterator() : ConcreteIterator*
21
}
22
23
Iterator <|-- ConcreteIterator
24
Aggregate <|-- ConcreteAggregate
25
ConcreteIterator --o ConcreteAggregate : aggregate
① Iterator(迭代器):
▮▮▮▮ⓐ 定义访问和遍历元素的接口。
② ConcreteIterator(具体迭代器):
▮▮▮▮ⓐ 实现 Iterator 接口。
▮▮▮▮ⓑ 对该聚合负责跟踪当前元素。
③ Aggregate(聚合):
▮▮▮▮ⓐ 定义创建相应迭代器对象的接口。
④ ConcreteAggregate(具体聚合):
▮▮▮▮ⓐ 实现创建相应迭代器的接口,该操作返回一个 ConcreteIterator 的实例。
16.6 参与者(Participants)
① Iterator (ListIterator)
▮▮▮▮ⓐ 定义访问和遍历元素的接口。
② ConcreteIterator (ConcreteListIterator)
▮▮▮▮ⓐ 实现 Iterator 接口。
③ Aggregate (List)
▮▮▮▮ⓐ 定义创建相应迭代器对象的接口。
④ ConcreteAggregate (ConcreteList)
▮▮▮▮ⓐ 实现创建相应迭代器的接口。
16.7 协作(Collaborations)
① ConcreteIterator 跟踪聚合中的当前元素,并(可能)需要保存聚合对象中的遍历位置。
16.8 效果(Consequences)
迭代器模式有以下优点和缺点:
① 优点:
▮▮▮▮ⓐ 它支持以不同的方式遍历一个聚合。
▮▮▮▮ⓑ 迭代器简化了聚合的接口。 聚合本身不需要实现遍历逻辑。
▮▮▮▮ⓒ 在同一个聚合上可以有多个遍历同时进行。
② 缺点:
▮▮▮▮ⓐ 对于一些简单的聚合,使用迭代器模式可能会显得过于复杂。
16.9 实现(Implementation)
以下是一个用 C++ 实现迭代器模式的示例,模拟了遍历一个简单的整数列表:
1
#include <iostream>
2
#include <vector>
3
4
// 前向声明
5
template <typename T> class ListIterator;
6
7
// 抽象聚合
8
template <typename T>
9
class List {
10
public:
11
using Iterator = ListIterator<T>;
12
13
void add(const T& element) {
14
elements_.push_back(element);
15
}
16
17
Iterator begin() {
18
return Iterator(this, 0);
19
}
20
21
Iterator end() {
22
return Iterator(this, elements_.size());
23
}
24
25
T get(int index) const {
26
if (index >= 0 && index < elements_.size()) {
27
return elements_[index];
28
}
29
throw std::out_of_range("Index out of bounds");
30
}
31
32
int size() const {
33
return elements_.size();
34
}
35
36
private:
37
std::vector<T> elements_;
38
};
39
40
// 具体迭代器
41
template <typename T>
42
class ListIterator {
43
public:
44
ListIterator(List<T>* list, int index) : list_(list), currentIndex_(index) {}
45
46
bool operator!=(const ListIterator& other) const {
47
return currentIndex_ != other.currentIndex_;
48
}
49
50
ListIterator& operator++() {
51
if (currentIndex_ < list_->size()) {
52
currentIndex_++;
53
}
54
return *this;
55
}
56
57
T operator*() const {
58
if (currentIndex_ < list_->size()) {
59
return list_->get(currentIndex_);
60
}
61
throw std::out_of_range("Iterator out of bounds");
62
}
63
64
private:
65
List<T>* list_;
66
int currentIndex_;
67
};
68
69
int main() {
70
List<int> numbers;
71
numbers.add(1);
72
numbers.add(2);
73
numbers.add(3);
74
numbers.add(4);
75
numbers.add(5);
76
77
std::cout << "Iterating through the list:" << std::endl;
78
for (auto it = numbers.begin(); it != numbers.end(); ++it) {
79
std::cout << *it << " ";
80
}
81
std::cout << std::endl;
82
83
return 0;
84
}
16.10 已知应用(Known Uses)
⚝ C++ 标准库中的迭代器。
⚝ Java 集合框架中的迭代器。
16.11 相关模式(Related Patterns)
① Composite(组合): 迭代器通常用于遍历组合结构。
② Factory Method(工厂方法): 可以使用工厂方法来创建迭代器。
③ Memento(备忘录): 可以使用备忘录模式来保存迭代器的状态。
17. Design Pattern 17: Mediator(中介者)
17.1 意图(Intent)
用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
17.2 别名(Also Known As)
⚝ Controller
17.3 动机(Motivation)
在一个系统中,如果对象之间的交互非常复杂,那么对象之间的耦合度可能会很高,这会导致系统难以维护和扩展。中介者模式通过引入一个中介对象来集中处理对象之间的交互,从而降低对象之间的耦合度。
每个对象都与中介者对象进行通信,而不是直接与其他对象通信。中介者对象知道系统中所有对象的存在,并负责协调它们之间的交互。这样,当对象之间的交互逻辑发生变化时,只需要修改中介者对象,而不需要修改各个对象本身。
17.4 适用性(Applicability)
在以下情况下可以使用中介者模式:
① 一组对象以定义良好但是复杂的方式进行通信。产生的相互依赖关系结构混乱且难以理解。
② 一个对象的行为依赖于其他对象,但是你不想直接了解这些对象。
③ 你想封装一组分布在多个类中但内聚的行为。
17.5 结构(Structure)
1
classDiagram
2
class Mediator {
3
<<abstract>>
4
+mediate(Component, string)
5
}
6
class ConcreteMediator {
7
+mediate(Component, string)
8
}
9
class Component {
10
// ...
11
}
12
class ConcreteComponent1 {
13
// ...
14
}
15
class ConcreteComponent2 {
16
// ...
17
}
18
19
Mediator <|-- ConcreteMediator
20
ConcreteMediator --o ConcreteComponent1
21
ConcreteMediator --o ConcreteComponent2
22
Component --o Mediator : mediator
23
ConcreteComponent1 --|> Component
24
ConcreteComponent2 --|> Component
① Mediator(中介者):
▮▮▮▮ⓐ 定义一个接口用于与各个 Colleague 对象通信。
② ConcreteMediator(具体中介者):
▮▮▮▮ⓐ 实现 Mediator 接口。
▮▮▮▮ⓑ 维护 Colleague 对象的引用。
▮▮▮▮ⓒ 协调 Colleague 对象之间的交互。
③ Colleague(同事):
▮▮▮▮ⓐ 定义与 Mediator 对象通信的接口。
▮▮▮▮ⓑ 知道 Mediator 对象。
▮▮▮▮ⓒ 当状态发生变化时,通知 Mediator。
④ ConcreteColleague(具体同事):
▮▮▮▮ⓐ 实现 Colleague 接口。
▮▮▮▮ⓑ 与其他同事通过 Mediator 进行通信。
17.6 参与者(Participants)
① Mediator (DialogDirector)
▮▮▮▮ⓐ 定义一个接口用于与各个 Colleague 对象通信。
② ConcreteMediator (FontDialogDirector)
▮▮▮▮ⓐ 实现 Mediator 接口。
③ Colleague (Control)
▮▮▮▮ⓐ 定义与 Mediator 对象通信的接口。
④ ConcreteColleague (ListBox, TextBox, Button)
▮▮▮▮ⓐ 实现 Colleague 接口。
17.7 协作(Collaborations)
① Colleague 对象通过调用 Mediator 对象上的操作来发送和接收消息。
② Mediator 对象实现协作行为,它通过调用其他 Colleague 对象上的操作来分发消息。
17.8 效果(Consequences)
中介者模式有以下优点和缺点:
① 优点:
▮▮▮▮ⓐ 减少了子类化。 Mediator 将分布在多个对象中的行为集中起来。可以通过子类化 Mediator 来改变应用的协作方式。
▮▮▮▮ⓑ 它将各个 Colleague 解耦。 Mediator 通过避免对象显式地相互引用来促进松耦合。
▮▮▮▮ⓒ 简化了对象协议。 用一个 Mediator 对象控制交互,可以避免 Colleague 之间复杂的网状连接。
② 缺点:
▮▮▮▮ⓐ Mediator 使控制集中化。 可能会导致 Mediator 对象过于复杂。
17.9 实现(Implementation)
以下是一个用 C++ 实现中介者模式的示例,模拟了一个简单的聊天室:
1
#include <iostream>
2
#include <string>
3
#include <vector>
4
#include <map>
5
6
// 前向声明
7
class User;
8
9
// 抽象中介者:聊天室
10
class ChatRoom {
11
public:
12
virtual ~ChatRoom() = default;
13
virtual void registerUser(User* user) = 0;
14
virtual void sendMessage(const std::string& message, User* sender) = 0;
15
};
16
17
// 具体中介者:具体聊天室
18
class ConcreteChatRoom : public ChatRoom {
19
public:
20
void registerUser(User* user) override {
21
users_[user->getName()] = user;
22
}
23
void sendMessage(const std::string& message, User* sender) override {
24
for (const auto& pair : users_) {
25
if (pair.second != sender) {
26
pair.second->receiveMessage(sender->getName() + ": " + message);
27
}
28
}
29
}
30
private:
31
std::map<std::string, User*> users_;
32
};
33
34
// 抽象同事:用户
35
class User {
36
public:
37
User(const std::string& name, ChatRoom* room) : name_(name), room_(room) {
38
room_->registerUser(this);
39
}
40
virtual ~User() = default;
41
std::string getName() const { return name_; }
42
virtual void sendMessage(const std::string& message) {
43
room_->sendMessage(message, this);
44
}
45
virtual void receiveMessage(const std::string& message) = 0;
46
47
protected:
48
std::string name_;
49
ChatRoom* room_;
50
};
51
52
// 具体同事:具体用户
53
class ConcreteUser : public User {
54
public:
55
ConcreteUser(const std::string& name, ChatRoom* room) : User(name, room) {}
56
void receiveMessage(const std::string& message) override {
57
std::cout << getName() << " received: " << message << std::endl;
58
}
59
};
60
61
int main() {
62
ConcreteChatRoom chatRoom;
63
64
ConcreteUser user1("Alice", &chatRoom);
65
ConcreteUser user2("Bob", &chatRoom);
66
ConcreteUser user3("Charlie", &chatRoom);
67
68
user1.sendMessage("Hello everyone!");
69
user2.sendMessage("Hi Alice!");
70
user3.sendMessage("Hey guys!");
71
72
return 0;
73
}
17.10 已知应用(Known Uses)
⚝ GUI 工具包中的对话框管理器。
⚝ 机场交通管制系统。
⚝ 聊天室服务器。
17.11 相关模式(Related Patterns)
① Facade(外观): 中介者模式与外观模式类似,但中介者定义了对象之间的通信和控制逻辑,而外观模式只是提供了一个简化的接口。
② Observer(观察者): 中介者模式可以用于实现观察者模式中的主题和观察者之间的通信。
③ Command(命令): 可以使用命令模式来封装中介者对象之间的请求。
18. Design Pattern 18: Memento(备忘录)
18.1 意图(Intent)
在不破坏对象封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到先前保存的状态。
18.2 别名(Also Known As)
⚝ Snapshot
18.3 动机(Motivation)
有时需要记录一个对象的内部状态,以便在以后能够恢复它。但是,直接暴露对象的内部状态可能会破坏对象的封装性。备忘录模式通过创建一个备忘录对象来存储对象的内部状态,从而解决了这个问题。
只有创建者对象(Originator)才能访问备忘录对象的内部状态。其他对象(Caretaker)只能存储和传递备忘录对象,而不能查看其内容。这样,就可以在不破坏对象封装性的前提下,保存和恢复对象的状态。
18.4 适用性(Applicability)
在以下情况下可以使用备忘录模式:
① 需要保存一个对象在某个时刻的(全部或部分)状态,这样以后需要时它才能恢复到先前保存的状态。
② 如果一个用接口得到状态会暴露实现的细节,并破坏对象的封装性。
18.5 结构(Structure)
1
classDiagram
2
class Originator {
3
+setState(State)
4
+getState() : State
5
+createMemento() : Memento
6
+setMemento(Memento)
7
}
8
class Memento {
9
+getState() : State
10
}
11
class Caretaker {
12
+addMemento(Memento)
13
+getMemento(int) : Memento
14
}
15
16
Originator --o Memento
17
Caretaker --o Memento
① Originator(发起人):
▮▮▮▮ⓐ 创建一个含有当前对象内部状态的备忘录。
▮▮▮▮ⓑ 使用备忘录来恢复其内部状态。
② Memento(备忘录):
▮▮▮▮ⓐ 存储 Originator 对象的内部状态。Originator 决定存储哪些内部状态。
▮▮▮▮ⓑ 防止 Originator 以外的其他对象访问备忘录的内容。
③ Caretaker(管理者):
▮▮▮▮ⓐ 负责保存备忘录。
▮▮▮▮ⓑ 不能查看备忘录的内容。
18.6 参与者(Participants)
① Originator (Document)
▮▮▮▮ⓐ 创建一个含有当前对象内部状态的备忘录。
▮▮▮▮ⓑ 使用备忘录来恢复其内部状态。
② Memento (DocumentState)
▮▮▮▮ⓐ 存储 Originator 对象的内部状态。
③ Caretaker (History)
▮▮▮▮ⓐ 负责保存备忘录。
18.7 协作(Collaborations)
① Caretaker 从 Originator 请求一个备忘录以保存 Originator 的内部状态。
② Originator 创建并返回一个包含其内部状态的备忘录。
③ Caretaker 将备忘录保存在安全的地方。
④ Caretaker 稍后可以将备忘录返回给 Originator,以恢复其先前的状态。
18.8 效果(Consequences)
备忘录模式有以下优点和缺点:
① 优点:
▮▮▮▮ⓐ 保持封装性。 Originator 可以保存其内部状态,而不会暴露其结构。
▮▮▮▮ⓑ 简化了 Originator。 Originator 不需要自己管理状态保存和恢复的细节。
② 缺点:
▮▮▮▮ⓐ 可能会消耗大量的存储空间,特别是当需要保存的状态很多或者很频繁时。
▮▮▮▮ⓑ 管理者可能需要维护备忘录的历史记录,这可能会增加管理的复杂性。
18.9 实现(Implementation)
以下是一个用 C++ 实现备忘录模式的示例,模拟了文本编辑器的撤销功能:
1
#include <iostream>
2
#include <string>
3
#include <vector>
4
5
// 备忘录类:编辑器状态
6
class EditorState {
7
public:
8
EditorState(const std::string& text) : text_(text) {}
9
std::string getText() const { return text_; }
10
11
private:
12
std::string text_;
13
};
14
15
// 发起人:文本编辑器
16
class TextEditor {
17
public:
18
void type(const std::string& text) {
19
content_ += text;
20
}
21
std::string getContent() const { return content_; }
22
23
EditorState* save() const {
24
return new EditorState(content_);
25
}
26
void restore(const EditorState* state) {
27
content_ = state->getText();
28
}
29
30
private:
31
std::string content_;
32
};
33
34
// 管理者:历史记录
35
class History {
36
public:
37
void push(EditorState* state) {
38
history_.push_back(state);
39
}
40
EditorState* pop() {
41
if (history_.empty()) {
42
return nullptr;
43
}
44
EditorState* lastState = history_.back();
45
history_.pop_back();
46
return lastState;
47
}
48
49
private:
50
std::vector<EditorState*> history_;
51
};
52
53
int main() {
54
TextEditor editor;
55
History history;
56
57
editor.type("Hello, ");
58
history.push(editor.save());
59
60
editor.type("world!");
61
history.push(editor.save());
62
63
std::cout << "Current content: " << editor.getContent() << std::endl;
64
65
EditorState* state1 = history.pop();
66
editor.restore(state1);
67
std::cout << "After first undo: " << editor.getContent() << std::endl;
68
delete state1;
69
70
EditorState* state2 = history.pop();
71
editor.restore(state2);
72
std::cout << "After second undo: " << editor.getContent() << std::endl;
73
delete state2;
74
75
return 0;
76
}
18.10 已知应用(Known Uses)
⚝ 文本编辑器的撤销/重做功能。
⚝ 数据库事务的回滚。
⚝ 游戏中的保存点。
18.11 相关模式(Related Patterns)
① Command(命令): 命令模式可以与备忘录模式一起使用,以实现撤销和重做功能。
② Iterator(迭代器): 可以使用备忘录模式来保存迭代器的状态。
19. Design Pattern 19: Observer(观察者)
19.1 意图(Intent)
定义对象之间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
19.2 别名(Also Known As)
⚝ Dependents, Publish-Subscribe
19.3 动机(Motivation)
假设你需要创建一个系统,当某个数据对象的状态发生变化时,需要通知多个不同的视图对象进行更新。一种方法是在数据对象中直接维护一个视图对象的列表,并在状态变化时遍历列表并通知每个视图对象。但这会导致数据对象和视图对象之间紧密耦合,并且难以添加新的视图对象。
观察者模式通过定义一个主题(Subject)接口和一个观察者(Observer)接口来解决这个问题。主题对象维护一个观察者对象的列表,并提供方法来添加和删除观察者。当主题对象的状态发生变化时,它会遍历观察者列表,并调用每个观察者的更新方法。这样,主题对象和观察者对象之间松散耦合,并且可以方便地添加新的观察者。
19.4 适用性(Applicability)
在以下情况下可以使用观察者模式:
① 当一个对象的改变需要同时改变其他对象,而不知道具体有多少对象需要改变时。
② 当一个对象必须通知其他对象,而它又不想知道这些对象是什么时。
③ 当在应用的不同部分之间形成“模型-视图”关系时。
19.5 结构(Structure)
1
classDiagram
2
class Subject {
3
+attach(Observer*)
4
+detach(Observer*)
5
+notify()
6
}
7
class ConcreteSubject {
8
+getState()
9
+setState(State)
10
+notify()
11
}
12
class Observer {
13
<<abstract>>
14
+update()
15
}
16
class ConcreteObserver1 {
17
+update()
18
}
19
class ConcreteObserver2 {
20
+update()
21
}
22
23
Subject --o Observer : observers
24
Subject <|-- ConcreteSubject
25
Observer <|-- ConcreteObserver1
26
Observer <|-- ConcreteObserver2
27
ConcreteObserver1 --o ConcreteSubject : subject
28
ConcreteObserver2 --o ConcreteSubject : subject
① Subject(主题):
▮▮▮▮ⓐ 知道它的观察者。任何数量的 Observer 对象都可以订阅一个主题。
▮▮▮▮ⓑ 提供一个接口用于附加(attach)和分离(detach) Observer 对象。
② Observer(观察者):
▮▮▮▮ⓐ 为那些在主题状态发生改变时需要获得通知的对象定义一个更新接口。
③ ConcreteSubject(具体主题):
▮▮▮▮ⓐ 将有关状态存入 ConcreteObserver 对象。
▮▮▮▮ⓑ 当它的状态发生改变时,向它的各个观察者发出通知。
④ ConcreteObserver(具体观察者):
▮▮▮▮ⓐ 维护一个指向 ConcreteSubject 对象的引用。
▮▮▮▮ⓑ 存储的状态应该与主题的状态保持一致。
▮▮▮▮ⓒ 实现 Observer 的更新接口以使其自身状态与主题的状态保持一致。
19.6 参与者(Participants)
① Subject (ClockTimer)
▮▮▮▮ⓐ 知道它的观察者。
② Observer (AnalogClock, DigitalClock)
▮▮▮▮ⓐ 为那些在主题状态发生改变时需要获得通知的对象定义一个更新接口。
③ ConcreteSubject
▮▮▮▮ⓐ 将有关状态存入 ConcreteObserver 对象。
④ ConcreteObserver
▮▮▮▮ⓐ 实现 Observer 的更新接口。
19.7 协作(Collaborations)
① 当 ConcreteSubject 的状态改变时,它将调用它的所有 ConcreteObserver 的 Update
操作。
② 在调用 Update
之后,每一个观察者可能会通过查询主体来使它的状态与主体的状态协同。
19.8 效果(Consequences)
观察者模式有以下优点和缺点:
① 优点:
▮▮▮▮ⓐ 主题和观察者之间的抽象耦合。 主题不需要知道具体的观察者是谁,只需要知道它们实现了观察者接口。
▮▮▮▮ⓑ 支持广播通信。 当主题的状态发生变化时,可以自动通知所有注册的观察者。
▮▮▮▮ⓒ 符合“开闭原则”。 可以很容易地添加新的观察者,而无需修改主题的代码。
② 缺点:
▮▮▮▮ⓐ 如果观察者过多,通知的开销可能会很大。
▮▮▮▮ⓑ 在某些情况下,可能会导致“级联更新”的问题,即一个观察者的更新导致其他观察者也需要更新。
▮▮▮▮ⓒ 主题和观察者之间的依赖关系可能难以追踪。
19.9 实现(Implementation)
以下是一个用 C++ 实现观察者模式的示例,模拟了股票价格的更新通知:
1
#include <iostream>
2
#include <string>
3
#include <vector>
4
#include <algorithm>
5
6
// 抽象观察者
7
class Observer {
8
public:
9
virtual ~Observer() = default;
10
virtual void update(double price) = 0;
11
};
12
13
// 具体观察者:股票投资者
14
class StockInvestor : public Observer {
15
public:
16
StockInvestor(const std::string& name, const std::string& stockSymbol)
17
: name_(name), stockSymbol_(stockSymbol) {}
18
void update(double price) override {
19
std::cout << "Investor " << name_ << " received update for " << stockSymbol_
20
<< ": New price is $" << price << std::endl;
21
}
22
private:
23
std::string name_;
24
std::string stockSymbol_;
25
};
26
27
// 抽象主题
28
class Subject {
29
public:
30
virtual ~Subject() = default;
31
virtual void attach(Observer* observer) = 0;
32
virtual void detach(Observer* observer) = 0;
33
virtual void notify() = 0;
34
35
protected:
36
std::vector<Observer*> observers_;
37
};
38
39
// 具体主题:股票
40
class Stock : public Subject {
41
public:
42
Stock(const std::string& symbol, double price) : symbol_(symbol), price_(price) {}
43
44
void attach(Observer* observer) override {
45
observers_.push_back(observer);
46
}
47
48
void detach(Observer* observer) override {
49
observers_.erase(std::remove(observers_.begin(), observers_.end(), observer), observers_.end());
50
}
51
52
void notify() override {
53
for (Observer* observer : observers_) {
54
observer->update(price_);
55
}
56
}
57
58
void setPrice(double newPrice) {
59
if (price_ != newPrice) {
60
price_ = newPrice;
61
notify();
62
}
63
}
64
65
std::string getSymbol() const { return symbol_; }
66
double getPrice() const { return price_; }
67
68
private:
69
std::string symbol_;
70
double price_;
71
};
72
73
int main() {
74
Stock googleStock("GOOGL", 1500.00);
75
76
StockInvestor investor1("John", "GOOGL");
77
StockInvestor investor2("Jane", "GOOGL");
78
79
googleStock.attach(&investor1);
80
googleStock.attach(&investor2);
81
82
googleStock.setPrice(1505.50);
83
googleStock.setPrice(1510.75);
84
85
googleStock.detach(&investor1);
86
87
googleStock.setPrice(1512.00);
88
89
return 0;
90
}
19.10 已知应用(Known Uses)
⚝ GUI 工具包中的事件处理机制。
⚝ 模型-视图-控制器(MVC)架构中的模型和视图。
⚝ 发布-订阅消息系统。
19.11 相关模式(Related Patterns)
① Mediator(中介者): 观察者模式和中介者模式都用于降低对象之间的耦合度,但它们的方式不同。观察者模式通常用于一对多的依赖关系,而中介者模式用于集中处理对象之间的交互。
② Strategy(策略): 可以使用策略模式来定义不同的更新策略。
20. Design Pattern 20: State(状态)
20.1 意图(Intent)
允许一个对象在其内部状态改变时改变它的行为。对象看起来好像修改了它的类。
20.2 别名(Also Known As)
⚝ Objects for States
20.3 动机(Motivation)
假设你有一个表示交通灯的对象。交通灯有三种状态:红灯、黄灯和绿灯。交通灯的行为取决于当前的状态。例如,当收到一个“切换”事件时,如果当前是红灯,则切换到绿灯;如果是绿灯,则切换到黄灯;如果是黄灯,则切换到红灯。
一种方法是在交通灯类中使用条件语句来处理不同状态下的行为,但这会导致代码过于复杂,并且难以添加新的状态。状态模式通过将每种状态封装在一个独立的状态对象中来解决这个问题。交通灯对象持有一个当前状态对象的引用,并将与状态相关的行为委托给当前状态对象。当交通灯的状态发生变化时,只需要切换当前状态对象即可。
20.4 适用性(Applicability)
在以下情况下可以使用状态模式:
① 当一个对象的行为取决于它的状态,并且它必须在运行时刻根据它的状态改变它的行为时。
② 当一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态时。这个状态通常用一个或多个枚举常量表示。通常把一个状态表示为一个独立的类会将相关的行为局部化,并且将依赖于状态的值的新状态加入变得容易。
20.5 结构(Structure)
1
classDiagram
2
class State {
3
<<abstract>>
4
+handle1()
5
+handle2()
6
}
7
class ConcreteStateA {
8
+handle1()
9
+handle2()
10
}
11
class ConcreteStateB {
12
+handle1()
13
+handle2()
14
}
15
class Context {
16
+request1()
17
+request2()
18
+changeState(State*)
19
}
20
21
Context --o State : state
22
State <|-- ConcreteStateA
23
State <|-- ConcreteStateB
① Context(环境):
▮▮▮▮ⓐ 定义客户感兴趣的接口。
▮▮▮▮ⓑ 维护一个 ConcreteState 子类的实例,这个实例定义对象的当前状态。
② State(状态):
▮▮▮▮ⓐ 定义一个接口以封装与 Context 的一个特定状态相关的行为。
③ ConcreteState(具体状态):
▮▮▮▮ⓐ 每一个 ConcreteState 子类实现与 Context 的一个状态相关的行为。
20.6 参与者(Participants)
① State (TCPState)
▮▮▮▮ⓐ 定义一个接口以封装与 Context 的一个特定状态相关的行为。
② ConcreteState (TCPClosed, TCPEstablished, TCPListen)
▮▮▮▮ⓐ 每一个 ConcreteState 子类实现与 Context 的一个状态相关的行为。
③ Context (TCPConnection)
▮▮▮▮ⓐ 定义客户感兴趣的接口。
▮▮▮▮ⓑ 维护一个 ConcreteState 子类的实例,这个实例定义对象的当前状态。
20.7 协作(Collaborations)
① Context 将与状态相关的请求委托给当前的 ConcreteState 对象处理。
② Context 可以将自身作为一个参数传递给 State 对象,允许 State 对象访问 Context,并在必要时转换到另一个状态。
20.8 效果(Consequences)
状态模式有以下优点和缺点:
① 优点:
▮▮▮▮ⓐ 它将与特定状态相关的行为局部化,并且将不同状态的行为分割开来。 所有的与一个特定的状态相关的代码都存在于一个 ConcreteState 子类中。因为状态被表示为对象,所以可以把它们当作参数传递给方法,并且可以独立于 Context 对象而存在。
▮▮▮▮ⓑ 它使得状态转换显式化。
▮▮▮▮ⓒ State 对象可以被共享。
② 缺点:
▮▮▮▮ⓐ 可能会导致系统中出现大量的状态类。
20.9 实现(Implementation)
以下是一个用 C++ 实现状态模式的示例,模拟了灯泡的不同状态:
1
#include <iostream>
2
#include <string>
3
4
// 前向声明
5
class LightBulb;
6
7
// 抽象状态
8
class State {
9
public:
10
virtual ~State() = default;
11
virtual void on(LightBulb* bulb) = 0;
12
virtual void off(LightBulb* bulb) = 0;
13
virtual std::string getStateName() const = 0;
14
};
15
16
// 具体状态:灯亮
17
class OnState : public State {
18
public:
19
void on(LightBulb* bulb) override {
20
std::cout << "Light is already on." << std::endl;
21
}
22
void off(LightBulb* bulb) override {
23
std::cout << "Turning light off." << std::endl;
24
bulb->setState(new OffState());
25
}
26
std::string getStateName() const override {
27
return "On";
28
}
29
};
30
31
// 具体状态:灯灭
32
class OffState : public State {
33
public:
34
void on(LightBulb* bulb) override {
35
std::cout << "Turning light on." << std::endl;
36
bulb->setState(new OnState());
37
}
38
void off(LightBulb* bulb) override {
39
std::cout << "Light is already off." << std::endl;
40
}
41
std::string getStateName() const override {
42
return "Off";
43
}
44
};
45
46
// 环境:灯泡
47
class LightBulb {
48
public:
49
LightBulb() : state_(new OffState()) {}
50
~LightBulb() { delete state_; }
51
52
void setState(State* newState) {
53
delete state_;
54
state_ = newState;
55
}
56
57
void on() {
58
state_->on(this);
59
}
60
61
void off() {
62
state_->off(this);
63
}
64
65
std::string getStateName() const {
66
return state_->getStateName();
67
}
68
69
private:
70
State* state_;
71
};
72
73
int main() {
74
LightBulb bulb;
75
std::cout << "Initial state: " << bulb.getStateName() << std::endl;
76
77
bulb.on();
78
std::cout << "Current state: " << bulb.getStateName() << std::endl;
79
80
bulb.on();
81
82
bulb.off();
83
std::cout << "Current state: " << bulb.getStateName() << std::endl;
84
85
bulb.off();
86
87
bulb.on();
88
std::cout << "Current state: " << bulb.getStateName() << std::endl;
89
90
return 0;
91
}
20.10 已知应用(Known Uses)
⚝ 有限状态机。
⚝ TCP 连接的不同状态。
⚝ 文档编辑器的不同模式(例如,只读、编辑)。
20.11 相关模式(Related Patterns)
① Strategy(策略): 状态模式和策略模式的结构很相似,但它们的意图不同。状态模式用于定义基于对象内部状态的行为,而策略模式用于定义可以互换的算法。
② Flyweight(享元): 状态对象通常可以作为享元对象共享。
21. Design Pattern 21: Strategy(策略)
21.1 意图(Intent)
定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。本模式使得算法可以独立于使用它的客户而变化。
21.2 别名(Also Known As)
⚝ Policy
21.3 动机(Motivation)
假设你需要实现一个排序功能,并且需要支持多种不同的排序算法(例如,快速排序、归并排序、插入排序)。一种方法是在排序类中使用条件语句来选择不同的排序算法,但这会导致排序类过于复杂,并且难以添加新的排序算法。
策略模式通过将每个排序算法封装在一个独立的策略对象中来解决这个问题。排序类持有一个策略对象的引用,并将排序操作委托给当前策略对象。当需要使用不同的排序算法时,只需要替换策略对象即可。
21.4 适用性(Applicability)
在以下情况下可以使用策略模式:
① 许多相关的类仅仅是行为有异。“策略”提供了一种用多个行为中的一个行为来配置一个类的方式。
② 你需要使用一个算法的不同变体。例如,你可能会定义一些反映不同的空间/时间权衡的算法。当这些变体被实现为算法的类层次时(Strategy 模式),你可以使用这些类的某一个来配置环境。
③ 当算法的实现细节不应该暴露给客户时。当需要算法的客户不应该了解其使用的数据时。
④ 当你需要一个类所表现的行为在其生命周期中是可以动态改变的。
21.5 结构(Structure)
1
classDiagram
2
class Strategy {
3
<<abstract>>
4
+algorithmInterface()
5
}
6
class ConcreteStrategyA {
7
+algorithmInterface()
8
}
9
class ConcreteStrategyB {
10
+algorithmInterface()
11
}
12
class Context {
13
+contextInterface()
14
+setStrategy(Strategy*)
15
}
16
17
Context --o Strategy : strategy
18
Strategy <|-- ConcreteStrategyA
19
Strategy <|-- ConcreteStrategyB
① Strategy(策略):
▮▮▮▮ⓐ 声明所有支持的算法都要实现的接口。Context 使用这个接口来调用由一个 ConcreteStrategy 定义的算法。
② ConcreteStrategy(具体策略):
▮▮▮▮ⓐ 实现 Strategy 接口以实现一个算法。
③ Context(环境):
▮▮▮▮ⓐ 配置一个 ConcreteStrategy 对象。
▮▮▮▮ⓑ 维护一个 Strategy 对象的引用。
▮▮▮▮ⓒ 可以定义一个接口以使 Strategy 能够访问它的数据。
21.6 参与者(Participants)
① Strategy (SortStrategy)
▮▮▮▮ⓐ 声明所有支持的算法都要实现的接口。
② ConcreteStrategy (QuickSort, ShellSort, MergeSort)
▮▮▮▮ⓐ 实现 Strategy 接口以实现一个算法。
③ Context (Sorter)
▮▮▮▮ⓐ 配置一个 ConcreteStrategy 对象。
21.7 协作(Collaborations)
① Strategy 和 Context 相互作用以实现选定的算法。当算法被调用时,Context 可能会将一些数据传递给 Strategy。Context 自身对算法的实现细节一无所知。
② 客户通常会创建并传递一个 ConcreteStrategy 对象给 Context;这样 Context 的客户仅仅需要配置 Context 和传递它需要的 Strategy 对象。一旦策略被设置,Context 就可以在其任何行为中使用它。
21.8 效果(Consequences)
策略模式有以下优点和缺点:
① 优点:
▮▮▮▮ⓐ 定义了一系列可重用的算法和行为。
▮▮▮▮ⓑ 提供了一种用不同行为配置一个类的方式。
▮▮▮▮ⓒ 将算法的实现与使用它的客户代码分离。
▮▮▮▮ⓓ 符合“开闭原则”。 可以很容易地添加新的策略。
② 缺点:
▮▮▮▮ⓐ 客户必须知道所有的策略。
▮▮▮▮ⓑ 可能会导致系统中出现大量的策略类。
▮▮▮▮ⓒ Context 和 Strategy 之间的通信开销可能会比较大。
21.9 实现(Implementation)
以下是一个用 C++ 实现策略模式的示例,模拟了不同的支付方式:
1
#include <iostream>
2
#include <string>
3
4
// 抽象策略:支付策略
5
class PaymentStrategy {
6
public:
7
virtual ~PaymentStrategy() = default;
8
virtual void pay(double amount) = 0;
9
};
10
11
// 具体策略:信用卡支付
12
class CreditCardPayment : public PaymentStrategy {
13
public:
14
CreditCardPayment(const std::string& cardNumber, const std::string& expiryDate, const std::string& cvv)
15
: cardNumber_(cardNumber), expiryDate_(expiryDate), cvv_(cvv) {}
16
void pay(double amount) override {
17
std::cout << "Paid $" << amount << " using credit card " << cardNumber_ << std::endl;
18
}
19
private:
20
std::string cardNumber_;
21
std::string expiryDate_;
22
std::string cvv_;
23
};
24
25
// 具体策略:PayPal 支付
26
class PayPalPayment : public PaymentStrategy {
27
public:
28
PayPalPayment(const std::string& email) : email_(email) {}
29
void pay(double amount) override {
30
std::cout << "Paid $" << amount << " using PayPal account " << email_ << std::endl;
31
}
32
private:
33
std::string email_;
34
};
35
36
// 环境:购物车
37
class ShoppingCart {
38
public:
39
void setPaymentStrategy(PaymentStrategy* strategy) {
40
paymentStrategy_ = strategy;
41
}
42
void checkout(double amount) {
43
if (paymentStrategy_ != nullptr) {
44
paymentStrategy_->pay(amount);
45
} else {
46
std::cout << "Payment strategy not set." << std::endl;
47
}
48
}
49
private:
50
PaymentStrategy* paymentStrategy_;
51
};
52
53
int main() {
54
ShoppingCart cart;
55
56
CreditCardPayment creditCard("1234-5678-9012-3456", "12/25", "123");
57
cart.setPaymentStrategy(&creditCard);
58
cart.checkout(100.0);
59
60
PayPalPayment paypal("user@example.com");
61
cart.setPaymentStrategy(&paypal);
62
cart.checkout(50.0);
63
64
return 0;
65
}
21.10 已知应用(Known Uses)
⚝ 排序算法的选择。
⚝ 压缩算法的选择。
⚝ 缓存策略的选择。
⚝ 支付方式的选择。
21.11 相关模式(Related Patterns)
① State(状态): 状态模式和策略模式的结构很相似,但它们的意图不同。状态模式用于定义基于对象内部状态的行为,而策略模式用于定义可以互换的算法。
② Template Method(模板方法): 策略模式定义了算法,而模板方法定义了算法的骨架。它们可以一起使用。
22. Design Pattern 22: Template Method(模板方法)
22.1 意图(Intent)
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method 使得子类可以在不改变一个算法的结构的前提下,重新定义该算法的某些特定步骤。
22.2 别名(Also Known As)
⚝ None commonly known.
22.3 动机(Motivation)
假设你需要实现一个数据处理流程,该流程包含多个步骤。其中一些步骤是固定的,而另一些步骤可能因具体的数据处理方式而异。一种方法是在一个大的方法中实现整个流程,但这会导致代码难以维护和扩展。
模板方法模式通过定义一个包含固定步骤的抽象类,并将可变步骤延迟到子类中来实现这个流程。抽象类中的模板方法定义了算法的骨架,而子类通过实现抽象方法来定义具体的步骤。这样,就可以在不改变算法结构的情况下,灵活地定制算法的各个部分。
22.4 适用性(Applicability)
在以下情况下可以使用模板方法模式:
① 一次性实现一个算法的不变部分,并将可变的行为留给子类来实现。
② 当多个子类中具有公共的行为时,应该将它们提取出来并集中到一个父类中以避免代码重复。这是 Template Method 的一个很好的应用场景。首先识别出不同行为中的不变部分,并将它们提取出来放到一个模板方法中。然后将可变的部分定义为抽象的操作,留待子类实现。
③ 当你需要控制子类的扩展。Template Method 通过定义在哪个点上可以进行子类化,使得你可以控制子类行为的扩展。
22.5 结构(Structure)
1
classDiagram
2
class AbstractClass {
3
+templateMethod()
4
+primitiveOperation1()
5
+primitiveOperation2()
6
}
7
class ConcreteClass {
8
+primitiveOperation1()
9
+primitiveOperation2()
10
}
11
12
AbstractClass <|-- ConcreteClass
① AbstractClass(抽象类):
▮▮▮▮ⓐ 定义抽象的原始操作,它们留待子类实现。
▮▮▮▮ⓑ 实现一个模板方法,定义了一个算法的骨架。这个模板方法不仅调用原始操作,也调用其他操作,包括 AbstractClass 中定义的操作或者其他对象中的操作。
② ConcreteClass(具体类):
▮▮▮▮ⓐ 实现父类所声明的原始操作。
22.6 参与者(Participants)
① AbstractClass (Application)
▮▮▮▮ⓐ 定义抽象的原始操作。
▮▮▮▮ⓑ 实现一个模板方法。
② ConcreteClass (MyApplication)
▮▮▮▮ⓐ 实现父类所声明的原始操作。
22.7 协作(Collaborations)
① 子类实现抽象的原始操作,这些操作被父类定义的模板方法所调用。
22.8 效果(Consequences)
模板方法模式有以下优点和缺点:
① 优点:
▮▮▮▮ⓐ 它是一种代码复用的基本技术。 模板方法将不变的行为搬移到父类中,从而去除了子类中的重复代码。
▮▮▮▮ⓑ 它在父类中定义了算法的骨架,而允许子类为一个或多个步骤提供实现。 这样,子类可以在不改变算法结构的情况下,重新定义算法的某些特定步骤。
▮▮▮▮ⓒ Template Method 有助于在父类中形式化地定义通用的算法。
② 缺点:
▮▮▮▮ⓐ 为了实现算法的某些特定步骤,每个不同的实现都需要定义一个子类。 这可能会导致系统中类的个数增加。
▮▮▮▮ⓑ 有时模板方法中的抽象操作由子类实现,这可能会颠倒人们对于控制结构的直觉。 特别是在一个操作中调用“父类中定义的操作”,而这个操作又调用“子类中实现的操作”的时候。
22.9 实现(Implementation)
以下是一个用 C++ 实现模板方法模式的示例,模拟了不同类型的报告生成器:
1
#include <iostream>
2
#include <string>
3
4
// 抽象类:报告生成器
5
class ReportGenerator {
6
public:
7
// 模板方法
8
void generateReport() {
9
collectData();
10
formatData();
11
displayReport();
12
printFooter();
13
}
14
15
protected:
16
virtual void collectData() = 0;
17
virtual void formatData() = 0;
18
virtual void displayReport() {
19
std::cout << "Displaying report..." << std::endl;
20
}
21
virtual void printFooter() {
22
std::cout << "End of report." << std::endl;
23
}
24
};
25
26
// 具体类:简单报告生成器
27
class SimpleReportGenerator : public ReportGenerator {
28
protected:
29
void collectData() override {
30
std::cout << "Collecting simple data..." << std::endl;
31
data_ = "Simple Data";
32
}
33
void formatData() override {
34
std::cout << "Formatting simple data: " << data_ << std::endl;
35
formattedData_ = "Formatted: " + data_;
36
}
37
void displayReport() override {
38
std::cout << "Displaying simple report: " << formattedData_ << std::endl;
39
}
40
private:
41
std::string data_;
42
std::string formattedData_;
43
};
44
45
// 具体类:详细报告生成器
46
class DetailedReportGenerator : public ReportGenerator {
47
protected:
48
void collectData() override {
49
std::cout << "Collecting detailed data..." << std::endl;
50
detailedData_.push_back("Detail 1");
51
detailedData_.push_back("Detail 2");
52
}
53
void formatData() override {
54
std::cout << "Formatting detailed data..." << std::endl;
55
formattedDetailedData_ = "Detailed Report:\n";
56
for (const auto& detail : detailedData_) {
57
formattedDetailedData_ += "- " + detail + "\n";
58
}
59
}
60
void displayReport() override {
61
std::cout << formattedDetailedData_ << std::endl;
62
}
63
void printFooter() override {
64
std::cout << "--- End of detailed report ---" << std::endl;
65
}
66
private:
67
std::vector<std::string> detailedData_;
68
std::string formattedDetailedData_;
69
};
70
71
int main() {
72
ReportGenerator* simpleReport = new SimpleReportGenerator();
73
std::cout << "Generating Simple Report:" << std::endl;
74
simpleReport->generateReport();
75
std::cout << std::endl;
76
77
ReportGenerator* detailedReport = new DetailedReportGenerator();
78
std::cout << "Generating Detailed Report:" << std::endl;
79
detailedReport->generateReport();
80
81
delete simpleReport;
82
delete detailedReport;
83
84
return 0;
85
}
22.10 已知应用(Known Uses)
⚝ 各种框架中的生命周期管理(例如,Servlet 的 doGet
和 doPost
方法)。
⚝ 算法的骨架定义(例如,排序算法中的比较和交换步骤)。
22.11 相关模式(Related Patterns)
① Factory Method(工厂方法): 模板方法通常会调用工厂方法来创建对象。
② Strategy(策略): 模板方法定义了算法的骨架,而策略模式定义了可以互换的算法。它们可以一起使用,例如,模板方法中的某个步骤可以使用策略模式来选择不同的实现。
23. Design Pattern 23: Visitor(访问者)
23.1 意图(Intent)
表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
23.2 别名(Also Known As)
⚝ None commonly known.
23.3 动机(Motivation)
假设你需要对一个包含不同类型元素的复杂对象结构(例如,一个文档,其中包含文本、图片、视频等)执行多种不同的操作(例如,打印、导出、类型检查)。一种方法是在每个元素类中添加这些操作的方法,但这会导致元素类过于复杂,并且难以添加新的操作。
访问者模式通过将每个操作封装在一个独立的访问者对象中来解决这个问题。访问者对象定义了对每种元素类型的操作。对象结构提供一个接受访问者的方法,该方法会将访问者对象传递给结构中的每个元素。元素对象根据自身的类型调用访问者对象中相应的方法。这样,就可以在不修改元素类的情况下,添加新的操作。
23.4 适用性(Applicability)
在以下情况下可以使用访问者模式:
① 一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作。
② 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,并且你想避免让这些操作“污染”这些对象的类。Visitor 使得你可以将相关的操作集中起来定义在一个类中。当该对象结构被很多应用共享时,用 Visitor 模式让每个应用仅包含需要的那部分操作。
③ 定义对象结构的类很少改变,但经常需要在此结构上定义新的操作。改变对象结构类需要重新定义对所有访问者的接口,这可能需要很大的代价。如果对象结构经常改变,那么可能还是在这些类中定义这些操作更好一些。
23.5 结构(Structure)
1
classDiagram
2
class Visitor {
3
<<abstract>>
4
+visitConcreteElementA(ConcreteElementA)
5
+visitConcreteElementB(ConcreteElementB)
6
}
7
class ConcreteVisitor1 {
8
+visitConcreteElementA(ConcreteElementA)
9
+visitConcreteElementB(ConcreteElementB)
10
}
11
class ConcreteVisitor2 {
12
+visitConcreteElementA(ConcreteElementA)
13
+visitConcreteElementB(ConcreteElementB)
14
}
15
class Element {
16
<<abstract>>
17
+accept(Visitor)
18
}
19
class ConcreteElementA {
20
+accept(Visitor)
21
+operationA()
22
}
23
class ConcreteElementB {
24
+accept(Visitor)
25
+operationB()
26
}
27
class ObjectStructure {
28
+attach(Element)
29
+detach(Element)
30
+accept(Visitor)
31
}
32
33
Visitor <|-- ConcreteVisitor1
34
Visitor <|-- ConcreteVisitor2
35
Element <|-- ConcreteElementA
36
Element <|-- ConcreteElementB
37
ObjectStructure --* Element : elements
① Visitor(访问者):
▮▮▮▮ⓐ 为对象结构中的每一个 ConcreteElement 类声明一个 Visit 操作。操作的名称和签名标识了发送 Visit 请求的类的具体类型。这样就使得访问者可以确定正在访问的元素的具体类型。
② ConcreteVisitor(具体访问者):
▮▮▮▮ⓐ 实现每个由 Visitor 声明的操作。每个操作实现本访问者需要对集合中对应类的元素进行的操作。
③ Element(元素):
▮▮▮▮ⓐ 定义一个 Accept
操作,它以一个访问者为参数。
④ ConcreteElement(具体元素):
▮▮▮▮ⓐ 实现 Accept
操作,它以一个访问者为参数。这个操作的效果是调用访问者相应于 ConcreteElement 的 Visit
操作,从而让访问者可以访问到该元素。
⑤ ObjectStructure(对象结构):
▮▮▮▮ⓐ 枚举它的元素。
▮▮▮▮ⓑ 可以提供一个高层的接口以允许访问者访问它的元素。
▮▮▮▮ⓒ 可以是一个组合或者一个集合,例如一个列表或者一个无序集合。
23.6 参与者(Participants)
① Visitor (DocumentVisitor)
▮▮▮▮ⓐ 为对象结构中的每一个 ConcreteElement 类声明一个 Visit 操作。
② ConcreteVisitor (PrintingVisitor, SpellCheckingVisitor)
▮▮▮▮ⓐ 实现每个由 Visitor 声明的操作。
③ Element (DocumentPart)
▮▮▮▮ⓐ 定义一个 Accept
操作。
④ ConcreteElement (PlainText, BoldText, Hyperlink)
▮▮▮▮ⓐ 实现 Accept
操作。
⑤ ObjectStructure (Document)
▮▮▮▮ⓐ 枚举它的元素。
23.7 协作(Collaborations)
① 客户创建一个 ConcreteVisitor 对象,然后遍历对象结构,并用每一个元素来“接受”这个访问者。
② 当一个元素接受访问者时,它通过调用访问者对象上与该元素自身类型相对应的方法来回调该访问者对象。
③ 随后访问者就可以访问这个元素的状态。
23.8 效果(Consequences)
访问者模式有以下优点和缺点:
① 优点:
▮▮▮▮ⓐ Visitor 使得易于增加新的操作。 本质上,Visitor 通过增加一个新类而不是修改现有的数据结构来使得你可以增加新的操作。
▮▮▮▮ⓑ Visitor 将相关的行为集中起来。 Visitor 通过在一个类中定义一组相关的操作,实现了高内聚。它将这些操作从它们所作用的对象中分离出来。
② 缺点:
▮▮▮▮ⓐ 当对象结构中的类层次发生变化时,可能需要修改所有的访问者。 特别是当需要增加一个新的 ConcreteElement 类时,必须扩展每一个访问者以提供对这个新类的访问。
▮▮▮▮ⓑ Visitor 可能会破坏对象的封装性。 Visitor 模式的假设是这些操作需要访问一个对象的内部表示。
23.9 实现(Implementation)
以下是一个用 C++ 实现访问者模式的示例,模拟了对不同形状进行绘制和计算面积的操作:
1
#include <iostream>
2
#include <string>
3
#include <vector>
4
5
// 前向声明
6
class Circle;
7
class Rectangle;
8
9
// 抽象访问者
10
class Visitor {
11
public:
12
virtual ~Visitor() = default;
13
virtual void visit(Circle* circle) = 0;
14
virtual void visit(Rectangle* rectangle) = 0;
15
};
16
17
// 具体访问者:绘制访问者
18
class DrawVisitor : public Visitor {
19
public:
20
void visit(Circle* circle) override {
21
std::cout << "Drawing a circle with radius: " << circle->getRadius() << std::endl;
22
}
23
void visit(Rectangle* rectangle) override {
24
std::cout << "Drawing a rectangle with width: " << rectangle->getWidth()
25
<< " and height: " << rectangle->getHeight() << std::endl;
26
}
27
};
28
29
// 具体访问者:面积计算访问者
30
class AreaVisitor : public Visitor {
31
public:
32
void visit(Circle* circle) override {
33
double area = 3.14159 * circle->getRadius() * circle->getRadius();
34
std::cout << "Area of circle: " << area << std::endl;
35
}
36
void visit(Rectangle* rectangle) override {
37
double area = rectangle->getWidth() * rectangle->getHeight();
38
std::cout << "Area of rectangle: " << area << std::endl;
39
}
40
};
41
42
// 抽象元素
43
class Shape {
44
public:
45
virtual ~Shape() = default;
46
virtual void accept(Visitor* visitor) = 0;
47
};
48
49
// 具体元素:圆形
50
class Circle : public Shape {
51
public:
52
Circle(double radius) : radius_(radius) {}
53
void accept(Visitor* visitor) override {
54
visitor->visit(this);
55
}
56
double getRadius() const { return radius_; }
57
private:
58
double radius_;
59
};
60
61
// 具体元素:矩形
62
class Rectangle : public Shape {
63
public:
64
Rectangle(double width, double height) : width_(width), height_(height) {}
65
void accept(Visitor* visitor) override {
66
visitor->visit(this);
67
}
68
double getWidth() const { return width_; }
69
double getHeight() const { return height_; }
70
private:
71
double width_;
72
double height_;
73
};
74
75
int main() {
76
std::vector<Shape*> shapes;
77
shapes.push_back(new Circle(5));
78
shapes.push_back(new Rectangle(10, 20));
79
80
DrawVisitor drawVisitor;
81
AreaVisitor areaVisitor;
82
83
std::cout << "Drawing shapes:" << std::endl;
84
for (Shape* shape : shapes) {
85
shape->accept(&drawVisitor);
86
}
87
std::cout << std::endl;
88
89
std::cout << "Calculating areas:" << std::endl;
90
for (Shape* shape : shapes) {
91
shape->accept(&areaVisitor);
92
}
93
94
for (Shape* shape : shapes) {
95
delete shape;
96
}
97
98
return 0;
99
}
23.10 已知应用(Known Uses)
⚝ 编译器中的类型检查和代码生成。
⚝ 文档对象模型(DOM)的遍历和操作。
⚝ 数学表达式的求值和转换。
23.11 相关模式(Related Patterns)
① Composite(组合): 访问者模式通常用于遍历组合结构。
② Strategy(策略): 访问者模式可以看作是一种策略模式,它允许你对对象结构执行不同的操作。