0%

【设计模式】观察者模式

本篇介绍了观察者模式及相关的面向对象设计原则。观察者模式定义了对象之间的一对多依赖,当一个对象的状态改变时,他的所有依赖者都会收到通知并自动更新。

1 引例

有一个气象站系统需要构建,系统中包含三部分:

image-20230302131902396

其中气象站负责感应气象数据,WeatherData 对象从气象站获取数据并更新气象显示装置,气象显示装置有不同的显示界面,比如目前状况、气象统计、天气预报等等,未来还可能加入新的显示界面。

一种实现方式是:

1
2
3
4
5
6
7
8
9
10
11
12
13
class WeatherData{
//其他内容...

void measurementsChanged(){
float temp = getTemperature();
float humidity = getHumidity();
float pressure = getPressure();
// 调用各显示面板的更新函数,以更新各显示界面
currentConditionsDisplay.update(temp, humidity, pressure);
statisticsDisplay.update(temp, humidity, pressure);
forecastDisplay.update(temp, humidity, pressure);
}
};

显然这样的实现方式违背了上一节提到的诸多设计原则,比如没有把不变和变化的代码分开,如果后续需要加入新的显示面板,就要更改 WeatherData 的代码,再比如调用面板的update()方法没有面向接口编程,而是一个一个的调用了具体面板的接口。因此这样的设计会为后续的系统维护带来巨大的成本。

2 观察者模式

观察者模式定义了对象之间的一对多依赖,当一个对象的状态改变时,他的所有依赖者都会收到通知并自动更新。

image-20230302134222869

观察者模式通常通过构建 Subject 接口 Observer 接口来实现:

image-20230302134606606

观察者模式提供了一种对象设计,让主题对象和观察者对象之间松耦合。所谓松耦合是指两个对象之间依然可以交互,但彼此不清楚对方的细节。松耦合的好处在于,主题对象只知道观察者实现了 Observer 接口,而无需关注观察者具体是谁,做了什么或者任何其他细节。在代码运行的任何时候都可以增加或者删除观察者,主题不会受到任何影响,它只在状态改变时,遍历观察者列表并通知他们。当我们改变主题或者观察者任意一方时,另一方都不会受到任何影响,只要它们之间的接口依然被遵守。因此使用松耦合设计系统会更有的弹性,更容易应对变化,因为对象之间的互相依赖降到了最低。

设计原则:为了交互对象之间的松耦合而努力。

回顾上面的气象站项目,显然 WeatherData 对象就是主题,主题在实现的时候通常也被称为 Observable(可观察对象),也就是被观察者,而不同的显示面板就是观察者。

于是我们首先需要实现两个接口:

1
2
3
4
5
6
7
8
9
10
11
12
// 观察者
class Observer {
public:
virtual void update(float temp, float humidity, float pressure) = 0;
};
// 可观察者
class Observable {
public:
virtual void registerObserver(Observer* o) = 0;
virtual void removeObserver(Observer* o) = 0;
virtual void notifyObservers() = 0;
};

然后实现 WeatherData 类:

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 WeatherData : public Observable {
private:
std::list<Observer*> observers;
float temperature;
float humidity;
float pressure;
public:
virtual void registerObserver(Observer* o) {
observers.push_back(o);
}

virtual void removeObserver(Observer* o) {
observers.remove(o);
}

virtual void notifyObservers() {
std::list<Observer*>::iterator it = observers.begin();
while (it != observers.end()) {
(*it)->update(temperature, humidity, pressure);
++it;
}
}

void measurementsChanged() {
notifyObservers();
}

// 其他方法...
};

某个观察者显示面板的实现更为简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class CurrentConditionsDisplay : public Observer {
private:
float temperature;
float humidity;
Observable* weatherData; //绑定一个被观察者,用于注册或者取消观察
public:
CurrentConditionsDisplay(Observable* o) : weatherData(o) {}

virtual void update(float temp, float humidity, float pressure) {
this->temperature = temp;
this->humidity = humidity;
display();
}

void display() {
// 显示信息
}
};

这样一个简单的观察者模式就是实现完毕了,这是经典的观察者模式实现,但存在一些缺陷:

  • 需要继承,继承是强对象关系,只能对特定的观察者才有效,即必须是Observer抽象类的派生类才行
  • 观察者被通知的接口参数不支持变化,导致观察者不能应付接口的变化,并且这个观察者还不能带参数

3 改进的观察者模式

C++11 提供的仿函数和模板等特性可以解决上述问题。C++11 实现的观察者模式,内部维护了一个泛型函数列表,观察者只需要将观察者函数注册进来即可,消除了继承导致的强耦合。通知接口还使用了可变参数模板,支持任意数量的参数,消除了接口变化的影响。

改进之后的观察者模式和 C# 中的 event 类似,通过定义观察者的模板类型来限定观察者,即不要求观察者必须为某个派生类,只要求所有观察者使用相同的函数模板即可,当需要和原来不同的观察者时,只需要定义一个新的 event 类型即可。

需要注意的是 event 对象不能拷贝和复制,这可以通过 delete 关键字来实现。

一个改进的观察者模式实现如下:

observer.hpp

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
#ifndef _OBSERVER_HPP_
#define _OBSERVER_HPP_

#include <iostream>
#include <string>
#include <functional>
#include <map>

using std::cout;
using std::endl;
using std::string;
using std::map;

class NonCopyable {
public:
//禁用复制构造函数
NonCopyable(const NonCopyable& n) = delete; // deleted
//禁用赋值构造函数
NonCopyable& operator=(const NonCopyable& n) = delete; // deleted
NonCopyable() = default; // available
};

template <typename Func>
class Events : public NonCopyable {
private:
int m_observerId = 0; //观察者对应编号
map<int, Func> m_connections; // 观察者列表
// 保存观察者并分配观察者编号
template <typename F>
int Assign(F&& f) {
int k = m_observerId++;
m_connections.emplace(k, std::forward<F>(f));
return k;
}
public:
// 注册观察者,支持右值引用
int Connect(Func&& f) {
return Assign(f);
}

// 注册观察者,左值
int Connect(const Func& f) {
return Assign(f);
}

// 移除观察者
void Disconnect(int key) {
m_connections.erase(key);
}

// 通知所有观察者
template <typename ... Args>
void Notify(Args&& ... args) {
for(auto &it : m_connections) {
auto& func = it.second;
func(std::forward<Args>(args)...);
}
}
};

#endif

main.cpp

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
#include "observer.hpp"

using ObserverFunc = std::function<int(int, int)>;

class Observer {
public:
int operator() (int a, int b) {
cout << "Observer函数对象的事件2被调用" << endl;
int res = a + b;
return res;
}
};

class Observer2 {
public:
int observerFunc(int a, int b) {
cout << "Observer2成员函数事件3被调用" << endl;
int res = a + b;
return res;
}
};

int gobserverFunc(int a, int b) {
cout << "全局的gobserverFunc事件4被调用" << endl;
int res = a + b;
return res;
}

int main(void) {
Events<ObserverFunc> e;
// 调用基于lambda表达式的观察者函数
int lambdaID = e.Connect([](int a, int b){
int res = a+b;
cout << "lambda函数的事件1被调用" << endl;
return res;
});

// 调用仿函数的观察者函数
int obsID = e.Connect(Observer());

// 调用成员函数的观察者函数
Observer2 o2;
int obsID1 = e.Connect(std::bind(&Observer2::observerFunc, o2, std::placeholders::_1, std::placeholders::_2));

// 调用全局观察者函数
int gID4 = e.Connect(gobserverFunc);

e.Notify(1, 2);

return 0;
}
---- 本文结束 知识又增加了亿点点!----

文章版权声明 1、博客名称:LycTechStack
2、博客网址:https://lz328.github.io/LycTechStack.github.io/
3、本博客的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,请联系博主进行删除处理。
4、本博客所有文章版权归博主所有,如需转载请标明出处。