C++ 设计模式使用教程之二

大纲

观察者模式

观察者模式的概念

观察者模式(Observer Pattern)是一种行为型设计模式。用于建立对象之间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式的别名包括发布 - 订阅模式(Publish/Subscribe)、模型 - 视图模式(Model/View)、源 - 监听器模式(Source/Listener),或从属者模式(Dependents)。这种模式常用于事件处理、数据绑定和回调函数等场景‌。

  • 观察者模式的结构组成

    • Subject(主题 / 被观察者)
      • 定义 Subject 接口,负责维护观察者列表(注册、移除、通知)
      • 在状态发生变化时,通知所有观察者
    • Observer(观察者)
      • 定义更新接口,负责接收通知
      • 依赖于 Subject,当 Subject 的状态发生变化时作出反应
    • ConcreteSubject(具体主题)
      • 实现了 Subject 接口的具体类,包含状态数据
      • 状态改变时调用 notify() 方法通知所有观察者
    • ConcreteObserver(具体观察者)
      • 实现 Observer 接口的具体类
      • 当 Subject 的状态发生改变时,更新自身状态‌
  • 观察者模式的工作流程

    • 观察者注册到主题对象上(通常通过 attach() 函数)
    • 当主题对象的状态发生改变时,调用 notify() 函数
    • 每个注册的观察者收到通知并执行相应的 update() 函数
  • 观察者模式的优缺点

    • 优点:
      • 解耦:观察者和被观察者之间是松耦合的
      • 可扩展性强:可以动态添加或者移除观察者
      • 广播通信机制:适合一个事件影响多个对象的场景
    • 缺点:
      • 如果观察者数量很多,通知过程可能比较耗时
      • 如果观察者和主题之间的依赖链过长,可能会引发性能或调试问题(例如循环更新)
  • 观察者模式的使用场景

    • GUI 软件中,实现按钮点击通知监听器
    • 发布 / 订阅系统(如新闻、消息推送)
    • 数据模型变化后通知视图更新(MVC 模式)

观察者模式的使用

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
#include <iostream>
#include <unordered_map>
#include <list>

using namespace std;

// 观察者接口
class Observer {

public:
// 接收通知
virtual void update(int state) = 0;

// 虚析构函数
virtual ~Observer() {}

};

// 具体观察者
class ConcreteObserverA : public Observer {

public:
// 接收通知
void update(int state) override {
cout << "ConcreteObserverA receive message, state is " << state << endl;
}

};

// 具体观察者
class ConcreteObserverB : public Observer {

public:
// 接收通知
void update(int state) override {
cout << "ConcreteObserverB receive message, state is " << state << endl;
}

};

// 具体观察者
class ConcreteObserverC : public Observer {

public:
// 接收通知
void update(int state) override {
cout << "ConcreteObserverC receive message, state is " << state << endl;
}

};

// 主题接口(被观察者)
class Subject {
public:
// 注册观察者
virtual void attach(int state, Observer *observer) = 0;

// 移除观察者
virtual void detach(int state, Observer *observer) = 0;

// 通知观察者
virtual void notify(int state) = 0;

// 虚析构函数
virtual ~Subject() {}
};


// 具体主题(被观察者)
class ConcreteSubject : public Subject {

public:
// 注册观察者
void attach(int state, Observer *observer) {
auto it = _subMap.find(state);
if (it != _subMap.end()) {
it->second.push_back(observer);
} else {
list<Observer *> list;
list.push_back(observer);
_subMap.insert(make_pair(state, list));
}
}

// 移除观察者
void detach(int state, Observer *observer) {
auto it = _subMap.find(state);
if (it != _subMap.end()) {
list<Observer *> &list = it->second;
for (auto it = list.begin(); it != list.end();) {
if (*it == observer) {
it = list.erase(it);
} else {
++it;
}
}
}
}

// 通知观察者
void notify(int state) {
auto it = _subMap.find(state);
if (it != _subMap.end()) {
for (Observer *pObserver : it->second) {
pObserver->update(state);
}
}
}

private:
// 观察者的集合
unordered_map<int, list<Observer *>> _subMap;

};

int main() {
// 主题(被观察者)
ConcreteSubject subject;

// 观察者
ConcreteObserverA obsA;
ConcreteObserverB obsB;
ConcreteObserverC obsC;

// 注册观察者
subject.attach(1, &obsA);
subject.attach(1, &obsB);
subject.attach(2, &obsA);
subject.attach(2, &obsC);

// 移除观察者
subject.detach(2, &obsA);

// 通知观察者
subject.notify(1);
subject.notify(2);

return 0;
}

程序运行的输出结果如下:

1
2
3
ConcreteObserverA receive message, state is 1
ConcreteObserverB receive message, state is 1
ConcreteObserverC receive message, state is 2

适配器模式

适配器模式的概念

适配器模式(Adapter Pattern)是一种结构型设计模式,主要用于将一个类的接口转换成客户端所期望的另一个接口,使原本接口不兼容的类可以一起工作。它就像 “插头转换器” 一样,可以帮助两个无法直接连接的系统建立连接。简而言之,适配器模式可以让已有的类在不修改源码的情况下,能和其他类协同工作,解决 “接口不兼容” 的问题。

  • 适配器模式的结构组成

    • 目标接口(Target):客户端期望的接口。
    • 待适配的类(Adaptee):已有的接口,功能已经实现,但接口不符合要求。
    • 适配器(Adapter):将 Adaptee 的接口转换为 Target 的接口。
  • 适配器模式的优缺点

    • 优点:
      • 提高代码复用性。
      • 解耦系统与外部接口。
      • 避免修改已有代码(符合 “开闭原则”)。
    • 缺点:
      • 增加代码复杂性。
      • 过多使用适配器,可能会让系统变得难以理解和维护。
  • 适配器模式的使用场景

    • 系统需要使用一些现有的类,但这些类的接口与系统期望的不一致。
    • 想复用一些功能强大的旧代码,但不希望修改它们。

适配器模式的实现

适配器模式的常见实现方式有以下两种:

  • (1) 类适配器(使用继承实现),适配器继承自 Adaptee,并实现 Target 接口。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Target
class Target {
public:
virtual void request() = 0;
};

// Adaptee
class Adaptee {
public:
void specificRequest() {
cout << "Adaptee specific request" << endl;
}
};

// Adapter
class Adapter : public Target, private Adaptee {
public:
void request() override {
// 调用 Adaptee 的方法
specificRequest();
}
};
  • (2) 对象适配器(使用组合实现),适配器持有一个 Adaptee 对象的引用,并实现 Target 接口。
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
// Target
class Target {

public:
virtual void request() = 0;

};

// Adaptee
class Adaptee {

public:
void specificRequest() {
cout << "Adaptee specific request" << endl;
}

};

// Adapter
class Adapter : public Target {

private:
Adaptee* adaptee;

public:
Adapter(Adaptee* a) : adaptee(a) {

}

void request() override {
adaptee->specificRequest();
}

};

适配器模式的使用

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
#include <iostream>

using namespace std;

// VGA 接口
class VGA {

public:
// 纯虚函数,拥有纯虚函数的基类,通常称之为抽象类
virtual void play() = 0;

// 虚析构函数
virtual ~VGA() {}

};

// 支持 VGA 接口的投影仪
class Device01 : public VGA {

public:
void play() override {
cout << "使用 VGA 接口的投影仪播放视频" << endl;
}

};

// 电脑类(只支持 VGA 接口)
class Computer {

public:
// 由于电脑只支持 VGA 接口,所以该函数的参数也只能支持 VGA 接口的指针/引用
void playVideo(VGA *vga) {
vga->play();
}

};

// HDMI 接口
class HDMI {

public:
// 纯虚函数,拥有纯虚函数的基类,通常称之为抽象类
virtual void play() = 0;

// 虚析构函数
virtual ~HDMI() {}

};

// 支持 HDMI 接口的投影仪
class Device02 : public HDMI {

public:
void play() override {
cout << "使用 HDMI 接口的投影仪播放视频" << endl;
}

};

// 由于电脑(VGA接口)和投影仪(HDMI接口)无法直接相连,所以需要添加适配器类
class VGAToHDMIAdapter : public VGA {

public:
VGAToHDMIAdapter(HDMI *p) : pHdmi(p) {

}

// 此方法相当于转换器,将 HDMI 信号转换为 VGA 信号
void play() override {
pHdmi->play();
}

private :
HDMI *pHdmi;
};

int main() {
Computer computer;

Device01 dev1;
computer.playVideo(&dev1);

Device02 dev2;
VGAToHDMIAdapter adapter(&dev2);
computer.playVideo(&adapter);

return 0;
}

程序运行的输出结果如下:

1
2
使用 VGA 接口的投影仪播放视频
使用 HDMI 接口的投影仪播放视频

装饰器模式

装饰器模式的概念

装饰器模式(Decorator Pattern)是一种结构型设计模式,允许在不改变对象结构的前提下,动态地为对象添加额外功能。就像是给一个盒子套上漂亮的包装纸,同时还可以叠加多个包装(功能),而不会改变盒子本身。简而言之,装饰器模式的主要作用是动态地给对象 “增强功能”,避免使用继承导致类爆炸式增长。

  • 装饰器模式的结构组成

    • 抽象组件(Component):定义对象的接口(可以是抽象类或接口)。
    • 具体组件(ConcreteComponent):被装饰的原始对象,提供基本功能。
    • 抽象装饰器(Decorator):持有一个组件对象引用,并实现相同接口。
    • 具体装饰器(ConcreteDecorator):在原始功能的基础上添加新功能。
  • 装饰器模式的优缺点

    • 优点:
      • 灵活:可以动态组合不同功能。
      • 遵循 “开闭原则”:对扩展开放,对修改封闭。
      • 可以用于替代复杂的继承体系。
    • 缺点:
      • 多层装饰时,调试会变困难,结构可能会变复杂。
      • 如果组合太多层,可能会降低性能和代码可读性。
  • 装饰器模式的使用场景

    • 想要在运行时为对象动态添加功能。
    • 不想使用继承方式来扩展类(避免子类爆炸)。
    • 想要通过多个组合方式来构建不同功能的对象。

装饰器模式的使用

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
/**
* 装饰器模式
*/

#include <iostream>

using namespace std;

// 抽象组件
class Coffee {

public:
virtual string getDescription() = 0;

virtual double cost() = 0;

virtual ~Coffee() {}

};

// 具体组件
class BasicCoffee : public Coffee {

public:
string getDescription() override {
return "Basic Coffee";
}

double cost() override {
return 5.0;
}

};

// 抽象装饰器
class CoffeeDecorator : public Coffee {

protected:
Coffee *decoratedCoffee;

public:
CoffeeDecorator(Coffee *coffee) : decoratedCoffee(coffee) {}

};

// 具体装饰器
class Milk : public CoffeeDecorator {

public:
Milk(Coffee *coffee) : CoffeeDecorator(coffee) {}

string getDescription() override {
return decoratedCoffee->getDescription() + " + Milk";
}

double cost() override {
return decoratedCoffee->cost() + 1.0;
}

};

int main() {
Coffee *basicCoffee = new BasicCoffee();
Coffee *milk = new Milk(basicCoffee);
cout << milk->getDescription() << endl;
cout << milk->cost() << endl;

delete milk;
delete basicCoffee;
}

程序运行的输出结果如下:

1
2
Basic Coffee + Milk
6