The Observer Design Pattern in Cpp - Mike Shah - CppCon 2022
可以说是最最常用的设计模式了。
引入
以愤怒的小鸟为例,讨论游戏的 subsystem
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| // pesudo code --angrybirds.cpp
void BirdSlingshot() {
SimulateBirdPhysics();
if(CheckCollisions()) {
PlaySoundBirdNoise();
if(HitObject Pig) {
UpdateScore();
PlayPigSound();
SimulateObjectPhysics(pig);
}
LogResult();
}
}
|
这段代码显然逻辑嵌套什么的比较多,我们可以使用一些技巧来修复它。
设计模式
一段好的代码应该
设计模式基本上是一些可重用的模板,帮助我们解决一些软件工程中经常遇到的问题。
Model, View, Controller(MVC)
MVC 模型和我们即将讨论的观察者模式适应性很好。核心是将程序分为 3 个主要部分。
接受输入,并且将其转换为 model 或者 view 的命令。
观察者模式
观察者模式基础实现
观察者模式是指一个对象,称为 subject,维护了一个依赖列表,称之为 observers,当其状态改变时会自动通知 observers。通常的实现是调用其一个方法。
其中,subject 是你想要追踪的对象,observer 是想要根据其状态做出反应的对象。
经典的情况是,subject 和 observer 是一对多的关系。
有时,subject 也叫做 Publisher,observer 也叫做 Subscriber
简单的实现如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| class Subject {
public:
void AddObserver(Observer* observer) {
observers_.push_front(observer);
}
void RemoveObserver(Observer* observer) {
observers_.remove(observer);
}
void Notify() {
for (auto& o : observers_) {
o->OnNotify();
}
}
private:
std::forward_list<Observer*> observers_;
};
class Observer {
public:
Observer(std::string name) name_(name) {}
void OnNotify() {
std::cout << name_ << "Hello~" << std::endl;
}
private:
std::string name_;
};
|
但这只是个基础的实现,并不具备什么灵活性、扩展性,也许可以维护?
第二次尝试
现在我们将 Subject 转换为 ISubject,其作用于 IObserver
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
| class ISubject {
public:
ISubject() {}
virtual ~ISubject() {}
virtual void AddObserver(IObserver* observer) {
observers_.push_front(observer);
}
virtual void RemoveObserver(IObserver* observer) {
observers_.remove(observer);
}
virtual void Notify() {
for (auto& o : observers_) {
o->OnNotify();
}
}
private:
std::forward_list<IObserver*> observers_;
};
class SomeSubject : public ISbuject {
public:
};
class IObserver {
public:
virutal ~IObeserver() {}
virtual void OnNotify() = 0;
};
class Watcher : public IObserver {
public:
Watcher(std::string name) name_(name) {}
void OnNotify() {
std::cout << name_ << "Hello~" << std::endl;
}
private:
std::string name_;
};
|
这次比上次好一点,但仍需要更强大的灵活性。
subject 仍然需要 more power.
第三次尝试
现在,我们的每一个观察者都可能是一个特殊的系统。而此时 Subject 自然也需要存储不同类型的信息。
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
| // Subject 可以处理不同类型的 messages/events/keys
// 让我们的 Subject 支持不同的子系统,但是仍然解耦
class ISubject {
public:
virtual void AddObserver(int msg, IObserver* observer) {
auto it = observers_.find(msg);
if (it == observers_.end()) {
observers_[msg] = ObserversLists();
}
observers_[msg].push_front(observer);
}
virtual void NotifyAll() {
for (ObserversMap::iterator it = observers_.begin(); it != observers_.end(); ++it) {
for (auto& o : observers_[it->first]) {
o->OnNotify();
}
}
}
virtual void Notify(int msg) {
for(auto& o : observers_[msg]) {
o->OnNotify();
}
}
private:
// 两个 typedef,一个用于存储 observer list
// 还有一个 map,int 作为 key,区别不同类型的系统
using ObserversList = std::forward_list<IObserver*>;
using ObserversMap = std::map<int, ObserversList>;
ObserversMap oberservers_;
};
// 我们需要决定 message 的类型
class SomeSubject : public ISubjecet {
public:
enmu MessageTypes{PLAYSOUND, HANDLEPHYSICS, LOG}; // 为了方便所以用了 enum 而不是 enum class
}
class SoundEvent : public IObserver {
public:
explicit SoundEvent(const std::string& name) : name_(name) {}
void OnNotify() {
std::cout << name_ << "Sound engine do...\n";
}
std::string GetName() const override {return name_;}
private:
std::string name_;
};
class PhysicsEvent : public IObserver {
public:
explicit SoundEvent(const std::string& name) : name_(name) {}
void OnNotify() {
std::cout << name_ << "Physics engine do...\n";
}
std::string GetName() const override {return name_;}
private:
std::string name_;
};
|
现在就非常灵活,并且也可维护,扩展性也很好。
观察者模式的其他 tip
- 一些 C++ 方面改进
- 使用其他结构替代 std::forward_list
- 也许 observer 需要按一定权重排序(priority_queue)
- 也许 observer 需要常量时间查找(unordered_map)