Cpp 设计模式笔记——4. 行为型模式
Chain of Responsibility (责任链)
责任链模式是一种行为设计模式,将请求沿着处理者链进行发送。在收到请求后,每个处理者均可选择对请求进行处理, 或将其继续传递给下个处理者。
首先我们需要创建一个抽象处理者基类HandlerBase,对外提供了几个方法:
set_next:与另一个处理者建立后继关系,当前处理者可能把请求处理完成,也可能将请求传递给后继者;handle_request:尝试处理请求,为了避免在责任链中出现死循环,使用哈希表记录下曾经出现过的处理者,处理过程有三种结果:- 遇到了可以处理请求的对象,处理成功;
- 回到之前遇到的处理者,表明出现死循环,处理失败;
- 责任链查找到了尽头,即不存在后继者,处理失败。
HandlerBase还预留了几个接口:
can_handle:判断是否可以处理完成当前的请求,返回布尔值handle:处理当前的请求
从基类HandlerBase继承得到各种类型的具体处理者(例如HandlerA、HandlerB和HandlerC),
它们分别实现了不同版本的can_handle和handle,不同类型的处理者可以相互连接以组成责任链。
示例代码如下 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
// 抽象处理者基类
class HandlerBase {
public:
virtual ~HandlerBase() = default;
void set_next(std::shared_ptr<HandlerBase> nextHandler) {
m_next = nextHandler;
}
void handle_request(const std::string &request) {
std::unordered_set<HandlerBase *> visitedHandlers;
handle_request_internal(request, visitedHandlers);
}
protected:
virtual bool can_handle(const std::string &request) = 0;
virtual void handle(const std::string &request) = 0;
private:
std::shared_ptr<HandlerBase> m_next = nullptr;
void handle_request_internal(
const std::string &request,
std::unordered_set<HandlerBase *> &visitedHandlers) {
if (visitedHandlers.find(this) != visitedHandlers.end()) {
std::cout << "Circular reference detected. Aborting.\n";
return;
}
visitedHandlers.insert(this);
if (can_handle(request)) { handle(request); }
else if (m_next) {
m_next->handle_request_internal(request, visitedHandlers);
}
else {
std::cout << "No handler for request: " << request << '\n';
}
}
};
// 具体处理者A
class HandlerA : public HandlerBase {
protected:
bool can_handle(const std::string &request) override {
return request == "Request type A";
}
void handle(const std::string &request) override {
std::cout << "HandlerA handling: " << request << '\n';
}
};
// 具体处理者B
class HandlerB : public HandlerBase {
protected:
bool can_handle(const std::string &request) override {
return request == "Request type B";
}
void handle(const std::string &request) override {
std::cout << "HandlerB handling: " << request << '\n';
}
};
// 具体处理者C
class HandlerC : public HandlerBase {
protected:
bool can_handle(const std::string &request) override {
return request == "Request type C";
}
void handle(const std::string &request) override {
std::cout << "HandlerC handling: " << request << '\n';
}
};
int main() {
auto handler1 = std::make_shared<HandlerA>();
auto handler2 = std::make_shared<HandlerB>();
auto handler3 = std::make_shared<HandlerC>();
handler1->set_next(handler2);
handler2->set_next(handler3);
handler1->handle_request("Request type A");
handler1->handle_request("Request type B");
handler1->handle_request("Request type C");
handler1->handle_request("Request type D");
return 0;
}
运行结果如下 1
2
3
4HandlerA handling: Request type A
HandlerB handling: Request type B
HandlerC handling: Request type C
No handler for request: Request type D
责任链模式通常可以与组合模式结合,在组合模式中我们已经在对象之间建立了树结构,这非常利于实现责任链,请求可以很自然地从叶子节点向根节点方向传递。
对于责任链模式的应用,通常可以分为如下两种情景:
- 第一种情况是希望请求被尽早处理,例如GUI界面上的操作通常会先检查最小的组件能否处理完成,如果可以就直接处理请求,否则查找它的上一级组件,直到最上层的对象;
- 第二种情况是希望请求被传递给责任链的终点,例如对请求进行合法性检查,除了终点之外的每一个处理者都是检查者,分别负责一个方面的检查,如果请求不满足要求则将其丢弃,只有满足所有检查的请求才会被传递给真正的处理者。
Command (命令)
命令模式是一种行为设计模式,它可将请求转换为一个包含与请求相关的所有信息的独立对象。 转换方法能根据不同的请求将方法参数化、延迟请求执行或将其放入队列中,且能实现可撤销操作。
由于不允许使用函数作为一等公民,在面向对象语言中必须将函数包装为一个具有execute方法的命令对象,这样就得到了命令模式,实现过程如下:
- 第一步,定义抽象的命令基类
Command,预留名为execute的执行接口; - 第二步,定义命令的作用对象类型——接收者类
Light; - 第三步,定义具体的命令类型(
LightOnCommand、LightOffCommand),它需要实现execute方法,并且还需要在对象内部存储接收者指针; - 第四步,定义命令的调用对象类型
RemoteControl,它在内部存储若干个命令对象指针,在执行命令时调用对应的execute方法。
示例代码如下 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
// 命令接口
class Command {
public:
virtual ~Command() = default;
virtual void execute() = 0;
};
// 接收者类
class Light {
public:
void on() { std::cout << "Light is ON\n"; }
void off() { std::cout << "Light is OFF\n"; }
};
// 具体命令类 - 打开灯
class LightOnCommand : public Command {
private:
Light *m_light;
public:
explicit LightOnCommand(Light *l) : m_light(l) {}
void execute() override { m_light->on(); }
};
// 具体命令类 - 关灯
class LightOffCommand : public Command {
private:
Light *m_light;
public:
explicit LightOffCommand(Light *l) : m_light(l) {}
void execute() override { m_light->off(); }
};
// 调用者类
class RemoteControl {
private:
std::vector<std::shared_ptr<Command>> m_onCommands;
std::vector<std::shared_ptr<Command>> m_offCommands;
public:
explicit RemoteControl(size_t n) : m_onCommands(n), m_offCommands(n) {}
void setCommand(int slot, std::shared_ptr<Command> onCommand,
std::shared_ptr<Command> offCommand) {
m_onCommands[slot] = std::move(onCommand);
m_offCommands[slot] = std::move(offCommand);
}
void pressOnButton(int slot) {
if (m_onCommands[slot]) { m_onCommands[slot]->execute(); }
}
void pressOffButton(int slot) {
if (m_offCommands[slot]) { m_offCommands[slot]->execute(); }
}
};
int main() {
Light livingRoomLight;
Light kitchenLight;
RemoteControl remote(2);
remote.setCommand(0, std::make_shared<LightOnCommand>(&livingRoomLight),
std::make_shared<LightOffCommand>(&livingRoomLight));
remote.setCommand(1, std::make_shared<LightOnCommand>(&kitchenLight),
std::make_shared<LightOffCommand>(&kitchenLight));
remote.pressOnButton(0);
remote.pressOffButton(0);
remote.pressOnButton(1);
remote.pressOffButton(1);
return 0;
}
运行结果如下 1
2
3
4Light is ON
Light is OFF
Light is ON
Light is OFF
Iterator (迭代器)
迭代器模式是一种行为设计模式,可以在不暴露集合底层表现形式(列表、 栈和树等)的情况下对外提供遍历所有元素的方式。
迭代器的实现比较简单直观:
- 第一步,定义迭代器基类
Iterator,提供hasNext和next两个接口,用于判断是否遍历结束,以及遍历下一个元素。 - 第二步,定义对数组的迭代器类
ArrayIterator,它的构造需要提供数组的原始指针,以及数组长度,在内部需要记录一下当前位置,它需要实现next()方法,返回当前位置的元素副本,与此同时,当前位置自动后移,还需要实现hasNext方法判断是否越界。 - 第三步,定义集合基类
Collection,提供添加元素的接口add和创建迭代器的接口createIterator,后者应该返回一个可以遍历当前集合的迭代器对象。 - 第四步,定义具体的数组集合类
ArrayCollection,负责继承并实现集合基类Collection的两个接口。
示例代码如下,这里对集合中的数据类型使用了模板参数T
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
// 迭代器接口
template <typename T>
class Iterator {
public:
virtual bool hasNext() = 0;
virtual T next() = 0;
virtual ~Iterator() = default;
};
// 具体迭代器类
template <typename T>
class ArrayIterator : public Iterator<T> {
public:
ArrayIterator(T *array, int size) : m_array(array), m_size(size) {}
bool hasNext() override { return m_index < m_size; }
T next() override { return m_array[m_index++]; }
private:
T *m_array;
int m_size;
int m_index = 0;
};
// 集合接口
template <typename T>
class Collection {
public:
virtual void add(const T &element) = 0;
virtual std::unique_ptr<Iterator<T>> createIterator() const = 0;
virtual ~Collection() = default;
};
// 具体集合类
template <typename T>
class ArrayCollection : public Collection<T> {
public:
explicit ArrayCollection(int capacity) : m_capacity(capacity) {
m_array = new T[m_capacity];
}
~ArrayCollection() override { delete[] m_array; }
ArrayCollection(const ArrayCollection &) = delete;
ArrayCollection &operator=(const ArrayCollection &) = delete;
void add(const T &element) override {
if (m_size < m_capacity) { m_array[m_size++] = element; }
else { std::cerr << "Array is full. Cannot add more elements.\n"; }
}
std::unique_ptr<Iterator<T>> createIterator() const override {
return std::make_unique<ArrayIterator<T>>(m_array, m_size);
}
private:
T *m_array;
int m_capacity;
int m_size = 0;
};
int main() {
ArrayCollection<int> collection(10);
collection.add(1);
collection.add(2);
collection.add(3);
collection.add(4);
auto it = collection.createIterator();
while (it->hasNext()) {
std::cout << it->next() << " ";
}
std::cout << '\n';
return 0;
}
运行结果如下 1
1 2 3 4
在C++的STL中大量地使用了迭代器模式,STL中的算法对容器的访问几乎都是通过迭代器实现的, 并且STL实际上提供了很多种不同的迭代器,可以有很多种分类:
- 只读/只写/读写
- 正向/反向/双向/随机
- ...
关于STL中的迭代器,这里不做过多讨论。
Mediator (中介者)
中介者模式是一种行为设计模式,可以减少对象之间混乱无序的依赖关系。该模式会限制对象之间的直接交互,迫使它们通过一个中介者对象进行合作,从网状、无中心的直接交互变成了中心化的间接交互。
中介者的实现比较复杂,C++需要进行类型前置声明等合规性处理:
- 第一步,我们需要对同事类基类
ColleagueBase进行前置声明; - 第二步,定义中介者基类
MediatorBase,它预留了两个接口:addColleague:添加一个同事类对象指针send_by_mediator:在同事之间进行一对多的消息传递
- 第三步,定义同事类基类
ColleagueBase, 它的构造需要传入一个中介者基类指针和自身的唯一标识符id,提供如下接口:send:向其它同事发送消息receive:从其它同事接收消息
- 第四步,定义具体的同事类(
ColleagueA、ColleagueB和ColleagueC),具体实现基类的对应接口 - 第五步,定义具体的中介者类
Mediator,它在内部使用字典记录当前注册的所有同事类对象指针,使用它们的id作为字典索引,具体实现基类的相应接口。
示例代码如下 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
// 前置声明
class ColleagueBase;
// 中介者基类
class MediatorBase {
public:
virtual ~MediatorBase() = default;
virtual void
send_by_mediator(ColleagueBase *sender, const std::string &message,
const std::vector<std::string> &receiverIds) = 0;
virtual void addColleague(ColleagueBase *colleague) = 0;
};
// 同事类基类
class ColleagueBase {
protected:
MediatorBase *mediator;
std::string id;
public:
ColleagueBase(MediatorBase *m, std::string id)
: mediator(m), id(std::move(id)) {}
virtual void send(const std::string &message,
const std::vector<std::string> &receiverIds) = 0;
virtual void receive(const std::string &message) = 0;
virtual ~ColleagueBase() = default;
std::string getId() const { return id; }
};
// 具体的同事类 A
class ColleagueA : public ColleagueBase {
public:
ColleagueA(MediatorBase *m, const std::string &id) : ColleagueBase(m, id) {}
void send(const std::string &message,
const std::vector<std::string> &receiverIds) override {
std::cout << "[" << id << "] sends: \"" << message << "\" to {";
for (const auto &receiverId : receiverIds) {
std::cout << receiverId << ",";
}
std::cout << "}\n";
mediator->send_by_mediator(this, message, receiverIds);
}
void receive(const std::string &message) override {
std::cout << "[" << id << "] receives: \"" << message << "\""
<< '\n';
}
};
// 具体的同事类 B
class ColleagueB : public ColleagueBase {
public:
ColleagueB(MediatorBase *m, const std::string &id) : ColleagueBase(m, id) {}
void send(const std::string &message,
const std::vector<std::string> &receiverIds) override {
std::cout << "[" << id << "] sends: \"" << message << "\" to {";
for (const auto &receiverId : receiverIds) {
std::cout << receiverId << ",";
}
std::cout << "}\n";
mediator->send_by_mediator(this, message, receiverIds);
}
void receive(const std::string &message) override {
std::cout << "[" << id << "] receives: \"" << message << "\""
<< '\n';
}
};
// 具体的同事类 C
class ColleagueC : public ColleagueBase {
public:
ColleagueC(MediatorBase *m, const std::string &id) : ColleagueBase(m, id) {}
void send(const std::string &message,
const std::vector<std::string> &receiverIds) override {
std::cout << "[" << id << "] sends: \"" << message << "\" to {";
for (const auto &receiverId : receiverIds) {
std::cout << receiverId << ",";
}
std::cout << "}\n";
mediator->send_by_mediator(this, message, receiverIds);
}
void receive(const std::string &message) override {
std::cout << "[" << id << "] receives: \"" << message << "\""
<< '\n';
}
};
// 具体的中介者类
class Mediator : public MediatorBase {
private:
std::unordered_map<std::string, ColleagueBase *> m_colleagues;
public:
void addColleague(ColleagueBase *colleague) override {
std::string id = colleague->getId();
m_colleagues[id] = colleague;
}
void
send_by_mediator(ColleagueBase *sender, const std::string &message,
const std::vector<std::string> &receiverIds) override {
for (const auto &receiverId : receiverIds) {
auto it = m_colleagues.find(receiverId);
if (it != m_colleagues.end()) { it->second->receive(message); }
else {
std::cout << "Receiver with ID " << receiverId << " not found!"
<< '\n';
}
}
}
};
int main() {
Mediator mediator;
ColleagueA colleagueA(&mediator, "A");
ColleagueB colleagueB(&mediator, "B");
ColleagueC colleagueC(&mediator, "C");
mediator.addColleague(&colleagueA);
mediator.addColleague(&colleagueB);
mediator.addColleague(&colleagueC);
colleagueA.send("Hello, B and C!", {"B", "C"});
colleagueB.send("Hi, A and C!", {"A", "C"});
colleagueC.send("Hey, A!", {"A"});
return 0;
}
运行结果如下 1
2
3
4
5
6
7
8[A] sends: "Hello, B and C!" to {B,C,}
[B] receives: "Hello, B and C!"
[C] receives: "Hello, B and C!"
[B] sends: "Hi, A and C!" to {A,C,}
[A] receives: "Hi, A and C!"
[C] receives: "Hi, A and C!"
[C] sends: "Hey, A!" to {A,}
[A] receives: "Hey, A!"
Memento (备忘录)
备忘录模式是一种行为设计模式,允许在不暴露对象实现细节的情况下保存和恢复对象之前的状态(快照)。
备忘录模式的目的就是创建备份,为了避免暴露对象内部细节,
我们需要提供一个通用的数据类型——备忘录类Memento,备忘录的内部以某种格式持续存储着状态信息,在创建备忘录时需要提供状态信息,备忘录还支持导出状态信息的副本。
希望使用备忘录进行备份和恢复的类型被称为原发器类型Originator,它需要自行实现与备忘录类的交互:
exportToMemento:导出内部状态,用于创建一个备忘录对象并返回;setFromMemento:接收一个备忘录对象为参数,从其中获取状态信息,用于重置自身的内部状态。
为了更方便地对备忘录进行管理,我们还可以实现一个管理者类型Caretaker,它是存储备忘录对象的栈的简易封装:
提供pushMemento接口来存储备忘录对象,提供popMemento接口导出栈顶的备忘录对象。
示例代码如下 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// 备忘录类
class Memento {
private:
std::string m_state;
public:
explicit Memento(std::string s) : m_state(std::move(s)) {}
std::string getState() const { return m_state; }
};
// 原发器类
class Originator {
private:
std::string m_state;
public:
void setState(const std::string &s) {
std::cout << "Setting state to: " << s << '\n';
m_state = s;
}
std::string getState() const { return m_state; }
Memento exportToMemento() {
std::cout << "Saving state to Memento: " << m_state << '\n';
return Memento(m_state);
}
void setFromMemento(const Memento &memento) {
m_state = memento.getState();
std::cout << "Restored state from Memento: " << m_state << '\n';
}
};
// 管理者类
class Caretaker {
private:
std::stack<Memento> m_mementoStack;
public:
void pushMemento(const Memento &memento) { m_mementoStack.push(memento); }
Memento popMemento() {
if (m_mementoStack.empty()) {
throw std::runtime_error("No mementos to restore.");
}
Memento memento = m_mementoStack.top();
m_mementoStack.pop();
return memento;
}
};
int main() {
Originator originator;
Caretaker caretaker;
originator.setState("State 1");
caretaker.pushMemento(originator.exportToMemento());
originator.setState("State 2");
caretaker.pushMemento(originator.exportToMemento());
originator.setState("State 3");
originator.setFromMemento(caretaker.popMemento());
originator.setFromMemento(caretaker.popMemento());
return 0;
}
运行结果如下 1
2
3
4
5
6
7Setting state to: State 1
Saving state to Memento: State 1
Setting state to: State 2
Saving state to Memento: State 2
Setting state to: State 3
Restored state from Memento: State 2
Restored state from Memento: State 1
这里我们的实现比较简单,没有实用价值,但是稍微修改一下,将备忘录实现为二进制流,并且支持对文件读取和写入二进制流, 就可以支持对几乎所有对象的备份。
示例代码如下 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
// 备忘录类
class Memento {
private:
std::vector<char> m_stateData;
public:
explicit Memento(std::vector<char> data) : m_stateData(std::move(data)) {}
const std::vector<char> &getStateData() const { return m_stateData; }
};
// 原发器类
class Originator {
private:
std::string m_strState;
std::vector<double> m_doubleArray;
bool m_boolState;
public:
void setState(const std::string &str, const std::vector<double> &array,
bool b) {
m_strState = str;
m_doubleArray = array;
m_boolState = b;
std::cout << "Setting state to: " << m_strState << ", {";
for (double f : m_doubleArray) std::cout << f << " ";
std::cout << "}, " << m_boolState << '\n';
}
Memento exportToMemento() {
std::vector<char> stateData;
// Save string length and string
size_t strLength = m_strState.size();
stateData.resize(sizeof(strLength) + strLength);
std::memcpy(stateData.data(), &strLength, sizeof(strLength));
std::memcpy(stateData.data() + sizeof(strLength), m_strState.data(),
strLength);
// Save double array size and elements
size_t arraySize = m_doubleArray.size();
size_t currentSize = stateData.size();
stateData.resize(currentSize + sizeof(arraySize)
+ arraySize * sizeof(double));
std::memcpy(stateData.data() + currentSize, &arraySize,
sizeof(arraySize));
std::memcpy(stateData.data() + currentSize + sizeof(arraySize),
m_doubleArray.data(), arraySize * sizeof(double));
// Save bool value
currentSize = stateData.size();
stateData.resize(currentSize + sizeof(m_boolState));
std::memcpy(stateData.data() + currentSize, &m_boolState,
sizeof(m_boolState));
std::cout << "Exporting state to Memento\n";
return Memento(stateData);
}
void setFromMemento(const Memento &memento) {
const std::vector<char> &stateData = memento.getStateData();
auto it = stateData.begin();
// Load string length and string
int64_t strLength = 0;
std::memcpy(&strLength, &(*it), sizeof(strLength));
it += sizeof(strLength);
m_strState = std::string(it, it + strLength);
it += strLength;
// Load double array size and elements
int64_t arraySize = 0;
std::memcpy(&arraySize, &(*it), sizeof(arraySize));
it += sizeof(arraySize);
m_doubleArray.resize(arraySize);
std::memcpy(m_doubleArray.data(), &(*it), arraySize * sizeof(double));
it += arraySize * sizeof(double);
// Load bool value
std::memcpy(&m_boolState, &(*it), sizeof(m_boolState));
std::cout << "Restored state from Memento: " << m_strState << ", {";
for (double f : m_doubleArray) std::cout << f << " ";
std::cout << "}, " << m_boolState << '\n';
}
};
// 管理者类
class Caretaker {
public:
static void saveMemento(const Memento &memento,
const std::string &filename) {
std::ofstream outFile(filename, std::ios::binary);
if (outFile.is_open()) {
const std::vector<char> &stateData = memento.getStateData();
outFile.write(stateData.data(), (int64_t)stateData.size());
outFile.close();
}
else {
std::cerr << "Unable to open file for writing: " << filename
<< '\n';
}
}
static Memento loadMemento(const std::string &filename) {
std::ifstream inFile(filename, std::ios::binary);
if (inFile.is_open()) {
std::vector<char> stateData(
(std::istreambuf_iterator<char>(inFile)),
std::istreambuf_iterator<char>());
inFile.close();
std::cout << "Loaded state from file " << filename << '\n';
return Memento(stateData);
}
std::cerr << "Unable to open file for reading: " << filename
<< '\n';
throw std::runtime_error("File not found.");
}
};
int main() {
Originator originator;
originator.setState("State1", {1.1, 2.2, 3.3}, true);
Caretaker::saveMemento(originator.exportToMemento(), "state1.bin");
originator.setState("State2", {4.4, 5.5}, false);
Caretaker::saveMemento(originator.exportToMemento(), "state2.bin");
originator.setState("State3", {6.6, 7.7, 8.8, 9.9}, true);
originator.setFromMemento(Caretaker::loadMemento("state2.bin"));
originator.setFromMemento(Caretaker::loadMemento("state1.bin"));
return 0;
}
运行结果如下 1
2
3
4
5
6
7
8
9Setting state to: State1, {1.1 2.2 3.3 }, 1
Exporting state to Memento
Setting state to: State2, {4.4 5.5 }, 0
Exporting state to Memento
Setting state to: State3, {6.6 7.7 8.8 9.9 }, 1
Loaded state from file state2.bin
Restored state from Memento: State2, {4.4 5.5 }, 0
Loaded state from file state1.bin
Restored state from Memento: State1, {1.1 2.2 3.3 }, 1
这里我们实际上是在模仿实现MATLAB的load和save函数,但是实际效果远远比不了MATLAB,MATLAB有一套完整的机制来实现任意对象的序列化和反序列化,不需要我们对自定义类型进行手动实现。
Observer (观察者)
观察者模式是一种行为设计模式,在对象之间建立一种观察关系,使得在被观察对象的某个事件发生时,自动通知多个正在观察该对象的其他对象。
观察者模式的实现过程如下:
- 第一步,定义观察者基类
Observer,它对外预留了update接口,代表更新观察者状态,被观察者将会通过调用这个接口来通知观察者; - 第二步,定义被观察者基类
Subject,它对外预留了几个接口:registerObserver:注册新的观察者,与之建立观察关系removeObserver:移除指定的观察者,与之解出观察关系notifyObservers:通知所有正在观察当前对象的观察者
- 第三步,定义具体的被观察者类(
WeatherStation、StockMarket等),它们实际上在内部都需要使用一个观察者指针数组来记录所有注册的观察者,并且实现预留的三个接口,其中notifyObservers需要通知当前数组中的所有观察者。如果被观察者的状态被其它方法修改,需要主动触发notifyObservers来发出通知。 - 第四步,定义具体的观察者类(
DisplayDevice、StockDisplay等),需要负责实现update接口的具体行为,例如获取传递过来的参数并记录到日志中。
示例代码如下(为了让不同类型的被观察者可以使用不同参数来通知观察者,这里使用了C++中不定参数的模板类)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
// 抽象观察者基类
template <typename... Args>
class Observer {
public:
virtual void update(Args... args) = 0;
virtual ~Observer() = default;
};
// 抽象被观察者基类
template <typename... Args>
class Subject {
public:
virtual void
registerObserver(std::shared_ptr<Observer<Args...>> observer) = 0;
virtual void
removeObserver(std::shared_ptr<Observer<Args...>> observer) = 0;
virtual void notifyObservers(Args... args) = 0;
virtual ~Subject() = default;
};
// 具体被观察者类 - WeatherStation
class WeatherStation : public Subject<double, double, double> {
public:
void registerObserver(
std::shared_ptr<Observer<double, double, double>> observer) override {
m_observers.push_back(observer);
}
void removeObserver(
std::shared_ptr<Observer<double, double, double>> observer) override {
m_observers.erase(
std::remove(m_observers.begin(), m_observers.end(), observer),
m_observers.end());
}
void notifyObservers(double temperature, double humidity,
double pressure) override {
for (auto &observer : m_observers) {
observer->update(temperature, humidity, pressure);
}
}
void setMeasurements(double temp, double hum, double pres) {
notifyObservers(temp, hum, pres);
}
private:
std::vector<std::shared_ptr<Observer<double, double, double>>> m_observers;
};
// 具体观察者类 - DisplayDevice
class DisplayDevice : public Observer<double, double, double> {
public:
explicit DisplayDevice(std::string name) : m_deviceName(std::move(name)) {}
void update(double temperature, double humidity, double pressure) override {
std::cout << "Device " << m_deviceName
<< " - Temperature: " << temperature
<< "°C, Humidity: " << humidity << "%, Pressure: " << pressure
<< "hPa\n";
}
private:
std::string m_deviceName;
};
// 具体被观察者类 - StockMarket
class StockMarket : public Subject<std::string, double> {
public:
void registerObserver(
std::shared_ptr<Observer<std::string, double>> observer) override {
m_observers.push_back(observer);
}
void removeObserver(
std::shared_ptr<Observer<std::string, double>> observer) override {
m_observers.erase(
std::remove(m_observers.begin(), m_observers.end(), observer),
m_observers.end());
}
void notifyObservers(std::string stock, double price) override {
for (auto &observer : m_observers) { observer->update(stock, price); }
}
void setStockPrice(std::string stock, double price) {
notifyObservers(stock, price);
}
private:
std::vector<std::shared_ptr<Observer<std::string, double>>> m_observers;
};
// 具体观察者类 - StockDisplay
class StockDisplay : public Observer<std::string, double> {
public:
explicit StockDisplay(std::string name) : m_displayName(std::move(name)) {}
void update(std::string stock, double price) override {
std::cout << "StockDisplay " << m_displayName << " - Stock: " << stock
<< ", Price: " << price << "\n";
}
private:
std::string m_displayName;
};
// 主函数
int main() {
auto weatherStation = std::make_shared<WeatherStation>();
auto display1 = std::make_shared<DisplayDevice>("WeatherDisplay1");
auto display2 = std::make_shared<DisplayDevice>("WeatherDisplay2");
weatherStation->registerObserver(display1);
weatherStation->registerObserver(display2);
std::cout << "Weather Station Day 1:\n";
weatherStation->setMeasurements(25.0, 65.0, 1013.0);
std::cout << "Weather Station Day 2:\n";
weatherStation->setMeasurements(30.0, 70.0, 1012.0);
weatherStation->removeObserver(display1); // remove display1
std::cout << "Weather Station Day 3:\n";
weatherStation->setMeasurements(28.0, 60.0, 1011.0);
auto stockMarket = std::make_shared<StockMarket>();
auto stockDisplay1 = std::make_shared<StockDisplay>("StockDisplay1");
auto stockDisplay2 = std::make_shared<StockDisplay>("StockDisplay2");
stockMarket->registerObserver(stockDisplay1);
stockMarket->registerObserver(stockDisplay2);
std::cout << "Stock Market Update 1:\n";
stockMarket->setStockPrice("AAPL", 150.0);
std::cout << "Stock Market Update 2:\n";
stockMarket->setStockPrice("GOOGL", 2750.0);
stockMarket->removeObserver(stockDisplay1); // remove stockDisplay1
std::cout << "Stock Market Update 3:\n";
stockMarket->setStockPrice("AMZN", 3400.0);
return 0;
}
运行结果如下 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16Weather Station Day 1:
Device WeatherDisplay1 - Temperature: 25°C, Humidity: 65%, Pressure: 1013hPa
Device WeatherDisplay2 - Temperature: 25°C, Humidity: 65%, Pressure: 1013hPa
Weather Station Day 2:
Device WeatherDisplay1 - Temperature: 30°C, Humidity: 70%, Pressure: 1012hPa
Device WeatherDisplay2 - Temperature: 30°C, Humidity: 70%, Pressure: 1012hPa
Weather Station Day 3:
Device WeatherDisplay2 - Temperature: 28°C, Humidity: 60%, Pressure: 1011hPa
Stock Market Update 1:
StockDisplay StockDisplay1 - Stock: AAPL, Price: 150
StockDisplay StockDisplay2 - Stock: AAPL, Price: 150
Stock Market Update 2:
StockDisplay StockDisplay1 - Stock: GOOGL, Price: 2750
StockDisplay StockDisplay2 - Stock: GOOGL, Price: 2750
Stock Market Update 3:
StockDisplay StockDisplay2 - Stock: AMZN, Price: 3400
除了观察者模式,还有一个与之相似的订阅发布模式,这个模式并不在二十几个经典设计模式之中, 但是两者经常会被放在一起讨论,对订阅发布模式的学习见后续笔记。
State (状态)
状态模式是一种行为设计模式,让我们能在一个对象的内部状态变化时改变其行为,使其看上去就像改变了自身所属的类一样。
首先定义状态类基类State,然后继承得到具体的开状态OnState和闭状态OffState,状态类提供了handle接口。
接下来,定义持有状态的上下文类Light,它的构造需要提供一个状态作为初始状态,
提供的set_state方法用于修改或查看当前的内部状态,提供的show方法的调用效果由当前的内部状态决定。
示例代码如下 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// 状态类基类
class State {
public:
virtual void handle() = 0;
virtual ~State() = default;
};
// 具体状态类 - 开状态
class OnState : public State {
public:
void handle() override { std::cout << "The light is now ON.\n"; }
};
// 具体状态类 - 关状态
class OffState : public State {
public:
void handle() override { std::cout << "The light is now OFF.\n"; }
};
// 上下文类
class Light {
public:
explicit Light(std::unique_ptr<State> initialState)
: m_state(std::move(initialState)) {}
void set_state(std::unique_ptr<State> newState) {
m_state = std::move(newState);
}
void handle() { m_state->handle(); }
private:
std::unique_ptr<State> m_state;
};
int main() {
auto light = std::make_unique<Light>(std::make_unique<OffState>());
light->handle();
light->set_state(std::make_unique<OnState>());
light->handle();
light->set_state(std::make_unique<OffState>());
light->handle();
return 0;
}
运行结果如下 1
2
3The light is now OFF.
The light is now ON.
The light is now OFF.
状态模式与有限状态机的概念紧密相关,两者的侧重点有所不同:
- 状态模式:调用方法的效果由内部状态决定,通常对外提供修改和查询内部状态的方法;
- 有限状态机:状态之间的转移关系构成一个单向图,对外提供的方法通常需要输入一个状态,然后结合现有的内部状态,查询转移关系,得到新的内部状态。
Strategy (策略)
策略模式是一种行为设计模式,允许用户定义一系列算法,并将每种算法分别放入独立的类中,使算法的对象能够相互替换。
策略模式是非常简单显然的做法,只需要给上下文对象提供一个可执行的策略对象即可,在C/C++中使用回调函数其实都可以算作策略模式,对于Java这种没有函数只有对象的语言,才有必要专门定义策略类和对象。
首先我们需要定义策略基类StrategyBase,它预留了执行接口execute,然后派生出多个具体的策略类(StrategyA、StrategyB等)。
上下文类Context需要根据不同的策略执行performTask方法,它可以被赋予一个策略类指针,如果在调用performTask方法时持有策略类指针,就调用对应的执行方法execute,否则报错。
示例代码如下 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// 策略基类
class StrategyBase {
public:
virtual ~StrategyBase() = default;
virtual void execute() const = 0;
};
// 具体策略A
class StrategyA : public StrategyBase {
public:
void execute() const override {
std::cout << "execute: Strategy A\n";
}
};
// 具体策略B
class StrategyB : public StrategyBase {
public:
void execute() const override {
std::cout << "execute: Strategy B\n";
}
};
// 上下文类
class Context {
private:
std::unique_ptr<StrategyBase> m_strategy;
public:
Context &setStrategy(std::unique_ptr<StrategyBase> newStrategy) {
m_strategy = std::move(newStrategy);
return *this;
}
void performTask() const {
if (m_strategy) { m_strategy->execute(); }
else { std::cout << "No Strategy\n"; }
}
};
int main() {
Context context;
context.setStrategy(std::make_unique<StrategyA>()).performTask();
context.setStrategy(std::make_unique<StrategyB>()).performTask();
return 0;
}
Template Method (模板方法)
模板方法模式是一种行为设计模式,它在基类中定义了一个算法的框架,允许子类在不修改结构的情况下重写算法的特定步骤。
模板方法的实现很简单:抽象基类AbstractClass定义了算法框架(模板),对应的方法名称是templateMethod,它会依次调用很多子方法来完成算法的对应步骤:
- 有的子方法是虚函数,基类已经提供了默认实现,子类可以选择继承或重写
- 有的子方法是纯虚函数,子类必须负责具体实现
具体的派生类(ConcreteClassA、ConcreteClassB等)需要负责实现那些必要的步骤,可以选择重写那些已经有默认实现的步骤,
派生类仍然通过templateMethod来提供完整算法的使用。
示例代码如下 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
// 抽象类
class AbstractClass {
public:
virtual ~AbstractClass() = default;
// 模板方法,依次调用很多子方法来完成整个功能
void templateMethod() const {
std::string data = initialize_data();
data = base_operation_1(data);
data = required_operation_1(data);
data = base_operation_2(data);
data = required_operation_2(data);
data = hook(data);
finalize(data);
}
protected:
// 初始化数据的方法,子类可以覆盖它
virtual std::string initialize_data() const { return "Initial Data"; }
// 有默认的实现,子类可以覆盖它
virtual std::string base_operation_1(const std::string &data) const {
return data + " \n-> AbstractClass: base_operation_1";
}
// 有默认的实现,子类可以覆盖它
virtual std::string base_operation_2(const std::string &data) const {
return data + " \n-> AbstractClass: base_operation_2";
}
// 必须由子类负责实现
virtual std::string required_operation_1(const std::string &data) const = 0;
// 必须由子类负责实现
virtual std::string required_operation_2(const std::string &data) const = 0;
// 有默认的实现,子类可以覆盖它
virtual std::string hook(const std::string &data) const {
return data + " \n-> AbstractClass: hook";
}
// 有默认的实现,子类可以覆盖它
virtual void finalize(const std::string &data) const {
std::cout << "Final result: " << data << '\n';
}
};
// 具体类A
class ConcreteClassA : public AbstractClass {
protected:
std::string required_operation_1(const std::string &data) const override {
return data + " \n-> ConcreteClassA: required_operation_1";
}
std::string required_operation_2(const std::string &data) const override {
return data + " \n-> ConcreteClassA: required_operation_2";
}
std::string hook(const std::string &data) const override {
return data + " \n-> ConcreteClassA: hook";
}
};
// 具体类B
class ConcreteClassB : public AbstractClass {
protected:
std::string required_operation_1(const std::string &data) const override {
return data + " \n-> ConcreteClassB: required_operation_1";
}
std::string required_operation_2(const std::string &data) const override {
return data + " \n-> ConcreteClassB: required_operation_2";
}
};
int main() {
std::cout << "Using ConcreteClassA:\n";
auto concreteClassA = std::make_unique<ConcreteClassA>();
concreteClassA->templateMethod();
std::cout << "Using ConcreteClassB:\n";
auto concreteClassB = std::make_unique<ConcreteClassB>();
concreteClassB->templateMethod();
return 0;
}
运行结果如下 1
2
3
4
5
6
7
8
9
10
11
12
13
14Using ConcreteClassA:
Final result: Initial Data
-> AbstractClass: base_operation_1
-> ConcreteClassA: required_operation_1
-> AbstractClass: base_operation_2
-> ConcreteClassA: required_operation_2
-> ConcreteClassA: hook
Using ConcreteClassB:
Final result: Initial Data
-> AbstractClass: base_operation_1
-> ConcreteClassB: required_operation_1
-> AbstractClass: base_operation_2
-> ConcreteClassB: required_operation_2
-> AbstractClass: hook
Visitor (访问者)
访问者模式是一种行为设计模式,它能将算法与其所作用的对象隔离开来,使我们可以在不改变各元素类的前提下定义作用于这些元素类的新操作。
我们的情景是:考虑一个图形元素基类Shape,以及扩展类Circle和Rectangle,它们有各自不同的属性和方法。
现在希望在尽量少改动元素的前提下,定义很多对不同元素的访问操作,例如获取id,获取详细信息等,
达到下面的效果(这里shapes是包括不同的图形元素的集合)
1
2
3for (auto &shape : shapes) {
// visit the shape to show its id
}
为了实现上述效果,C++需要进行类型前置声明等合规性处理,因此代码看起来非常混乱:
第一步,我们需要对所有的派生自
Shape的具体图形元素类型进行前置声明;第二步,定义访问者基类
ShapeVisitor,它包括对每一个具体元素预留名为visit的访问接口,参数是对应元素指针;第三步,定义图形元素基类,它预留了一个名为
accept的接受访问接口,参数是访问者基类指针;第四步,定义所有的具体图形元素(
Circle和Rectangle等),它们都继承自Shape,需要实现接受访问者基类ShapeVisitor访问的接口,内容均为1
2
3void accept(ShapeVisitor *visitor) override {
visitor->visit(this);
}第五步,定义具体的访问者(
ShapeIdVisitor和ShapeDetailVisitor等),它们都继承自ShapeVisitor,需要负责实现对每一个具体元素类型的visit行为。
通过上述代码,我们将遍历集合进行的访问反转,实际实现的是下面的效果
1
2
3for (auto &shape : shapes) {
shape->accept(&idVisitor);
}
示例代码如下 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
// 前向声明
class Circle;
class Rectangle;
// 访问者接口
class ShapeVisitor {
public:
virtual void visit(Circle *shape) = 0;
virtual void visit(Rectangle *shape) = 0;
virtual ~ShapeVisitor() = default;
};
// 图形元素接口
class Shape {
public:
virtual void accept(ShapeVisitor *visitor) = 0;
virtual int getId() const = 0;
virtual ~Shape() = default;
};
// 圆形类
class Circle : public Shape {
public:
Circle(int id, double radius) : m_id(id), m_radius(radius) {}
void accept(ShapeVisitor *visitor) override { visitor->visit(this); }
int getId() const override { return m_id; }
double getRadius() const { return m_radius; }
private:
int m_id;
double m_radius;
};
// 矩形类
class Rectangle : public Shape {
public:
Rectangle(int id, double width, double height)
: m_id(id), m_width(width), m_height(height) {}
void accept(ShapeVisitor *visitor) override { visitor->visit(this); }
int getId() const override { return m_id; }
double getWidth() const { return m_width; }
double getHeight() const { return m_height; }
private:
int m_id;
double m_width;
double m_height;
};
// 获取图形ID的访问者类
class ShapeIdVisitor : public ShapeVisitor {
public:
void visit(Circle *shape) override {
std::cout << "Circle ID: " << shape->getId() << "\n";
}
void visit(Rectangle *shape) override {
std::cout << "Rectangle ID: " << shape->getId() << "\n";
}
};
// 获取图形详细信息的访问者类
class ShapeDetailVisitor : public ShapeVisitor {
public:
void visit(Circle *shape) override {
std::cout << "Circle ID: " << shape->getId()
<< ", Radius: " << shape->getRadius() << "\n";
}
void visit(Rectangle *shape) override {
std::cout << "Rectangle ID: " << shape->getId()
<< ", Width: " << shape->getWidth()
<< ", Height: " << shape->getHeight() << "\n";
}
};
int main() {
std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Circle>(1, 10.0));
shapes.push_back(std::make_unique<Rectangle>(2, 20.0, 15.0));
ShapeIdVisitor idVisitor;
ShapeDetailVisitor detailVisitor;
std::cout << "Using ShapeIdVisitor:\n";
for (auto &shape : shapes) { shape->accept(&idVisitor); }
std::cout << "Using ShapeDetailVisitor:\n";
for (auto &shape : shapes) { shape->accept(&detailVisitor); }
return 0;
}
运行结果如下 1
2
3
4
5
6Using ShapeIdVisitor:
Circle ID: 1
Rectangle ID: 2
Using ShapeDetailVisitor:
Circle ID: 1, Radius: 10
Rectangle ID: 2, Width: 20, Height: 15
需要注意的是:访问者模式其实是违背开闭原则的!虽然添加新的访问者类型是不需要修改其它代码的, 但是添加新的图形类是需要修改现有代码的,对于C++,我们还需要维护前置声明等很多语法细节。
