观察者模式(Observer Pattern)
观察者模式是一种行为型设计模式。
它解决的问题很简单:
一个对象的状态发生变化时,需要自动通知多个其他对象。
例如:
Qt 的 signal / slot
微信公众号:发布文章 → 多个用户收到通知
股票:股票价格变化 → 多个界面自动刷新
温度传感器:温度变化 → 温度显示、报警器、记录器同时更新
MVC:Model 数据变化 → View 自动刷新
一、先理解一个生活中的例子
假设有一个气象站:
气象站 │ 温度发生变化 │ ┌─────┼─────┐ ↓ ↓ ↓ 温度显示 手机App 报警器气象站并不需要知道每个设备具体如何处理。
它只需要:
通知所有观察者
每个观察者自己决定收到通知后做什么。
这就是观察者模式的核心思想。
二、观察者模式的结构
主要有两个角色:
1. Subject(主题/被观察者)
负责:
保存观察者
添加观察者
删除观察者
状态发生变化时通知观察者
2. Observer(观察者)
负责:
接收通知
根据通知执行自己的操作
结构:
Subject │ notify()│ ┌────────┼────────┐ ↓ ↓ ↓ ObserverA ObserverB ObserverC三、最简单的 C++ 实现
我们模拟一个温度传感器。
Observer.h#ifndef OBSERVER_H#define OBSERVER_Hclass Observer{public: virtual ~Observer(){}virtual void update(float temperature)=0;};#endif这里定义观察者接口。
所有观察者都必须实现:
update()
Subject
#ifndefSUBJECT_H#defineSUBJECT_H#include<vector>#include"observer.h"classSubject{public:virtual~Subject(){}voidattach(Observer*observer){m_observers.push_back(observer);}voiddetach(Observer*observer){for(autoit=m_observers.begin();it!=m_observers.end();++it){if(*it==observer){m_observers.erase(it);break;}}}protected:voidnotify(floattemperature){for(Observer*observer:m_observers){observer->update(temperature);}}private:std::vector<Observer*>m_observers;};#endif四、实现温度传感器
#ifndefTEMPERATURESENSOR_H#defineTEMPERATURESENSOR_H#include"subject.h"classTemperatureSensor:publicSubject{public:voidsetTemperature(floattemperature){m_temperature=temperature;// 温度发生变化notify(m_temperature);}floattemperature()const{returnm_temperature;}private:floatm_temperature=0;};#endif重点就是:
m_temperature = temperature;
notify(m_temperature);
温度变化之后,自动通知所有观察者。
五、创建具体观察者
温度显示器
#include<iostream>#include"observer.h"classTemperatureDisplay:publicObserver{public:voidupdate(floattemperature)override{std::cout<<"温度显示器:当前温度 = "<<temperature<<" ℃"<<std::endl;}};报警器
#include<iostream>#include"observer.h"classTemperatureAlarm:publicObserver{public:voidupdate(floattemperature)override{if(temperature>30){std::cout<<"报警器:温度过高!"<<std::endl;}}};日志记录器
#include<iostream>#include"observer.h"classTemperatureLogger:publicObserver{public:voidupdate(floattemperature)override{std::cout<<"日志:记录温度 "<<temperature<<" ℃"<<std::endl;}};六、测试
#include"temperaturesensor.h"intmain(){TemperatureSensor sensor;TemperatureDisplay display;TemperatureAlarm alarm;TemperatureLogger logger;sensor.attach(&display);sensor.attach(&alarm);sensor.attach(&logger);sensor.setTemperature(25);std::cout<<"----------------"<<std::endl;sensor.setTemperature(35);return0;}运行:
温度显示器:当前温度 = 25 ℃
日志:记录温度 25 ℃
温度显示器:当前温度 = 35 ℃
报警器:温度过高!
日志:记录温度 35 ℃
七、这个模式最重要的地方
注意:
TemperatureSensor
完全不知道:
TemperatureDisplay
TemperatureAlarm
TemperatureLogger
它只知道:
Observer
也就是说:
TemperatureSensor │ │ 依赖抽象 ↓ Observer ↑ │ ┌─────┼────────┐ ↓ ↓ ↓ 显示器 报警器 日志这就是观察者模式最核心的思想:
面向抽象,而不是面向具体观察者。
八、如果用 Qt,观察者模式就非常熟悉了
其实你平时写 Qt:
connect(button,
&QPushButton::clicked,
this,
&MainWindow::onClicked);
本质上就具有非常明显的观察者模式思想。
例如:
QPushButton │ │ clicked ↓ ┌───┼───────────────┐ ↓ ↓ ↓ 窗口 日志系统 其他对象按钮是被观察者/事件发送者。
其他对象是观察者。
Qt 使用:
signal
表示事件通知:
signals:
void temperatureChanged(float temperature);
然后:
connect(sensor,
&TemperatureSensor::temperatureChanged,
display,
&TemperatureDisplay::update);
这样温度变化的时候:
emit temperatureChanged(temperature);
所有连接的对象都会收到通知。
九、Qt 中写一个观察者模式实例
例如温度传感器:
classTemperatureSensor:publicQObject{Q_OBJECTpublic:voidsetTemperature(floattemperature){if(qFuzzyCompare(m_temperature,temperature))return;m_temperature=temperature;emittemperatureChanged(m_temperature);}signals:voidtemperatureChanged(floattemperature);private:floatm_temperature=0;};观察者:
classTemperatureDisplay:publicQObject{Q_OBJECTpublicslots:voidonTemperatureChanged(floattemperature){qDebug()<<"当前温度:"<<temperature;}};连接:
TemperatureSensor sensor;TemperatureDisplay display;QObject::connect(&sensor,&TemperatureSensor::temperatureChanged,&display,&TemperatureDisplay::onTemperatureChanged);然后:
sensor.setTemperature(25);
就会自动执行:
display.onTemperatureChanged(25);
十、观察者模式和发布-订阅的区别
这两个概念非常容易混。
观察者模式
通常是:
Subject │ ├──── Observer1 ├──── Observer2 └──── Observer3Subject 直接持有 Observer。
例如:
std::vector<Observer *> m_observers;
发布-订阅
通常中间还有一个消息中心:
Publisher │ ↓ MessageBus │ ┌──┼────┐ ↓ ↓ ↓ Sub1 Sub2 Sub3Publisher 不直接知道 Subscriber。
Qt 的 signal/slot、消息总线、事件总线等场景经常会出现这种思想。
十一、观察者模式的优点
① 解耦
比如:
TemperatureSensor
不需要知道:
TemperatureAlarm
所以报警器可以随时增加、删除。
② 支持一对多
一个对象变化:
Sensor │ ┌──────┼──────┐ ↓ ↓ ↓ 显示器 报警器 日志可以通知很多对象。
③ 扩展方便
以后增加:
TemperatureCloudUploader
只需要:
class TemperatureCloudUploader : public Observer
{
…
};
然后:
sensor.attach(&cloudUploader);
原来的 Sensor 不需要修改。
这符合开闭原则。
十二、观察者模式的缺点
① 观察者太多
如果:
Subject
├── Observer1
├── Observer2
├── …
└── Observer1000
一次通知可能触发大量操作。
② 通知链可能复杂
比如:
A变化
↓
通知B
↓
B变化
↓
通知C
↓
C变化
↓
通知A
甚至可能造成:
无限循环
所以实际项目中需要注意通知关系。
③ 生命周期问题
传统 C++ 实现:
std::vector<Observer*> m_observers;
如果 Observer 被释放了:
delete observer;
Subject 里面还保存着这个指针,就可能出现:
野指针
Qt 的信号槽在 QObject 生命周期管理方面就方便很多。
十三、什么时候适合使用?
看到下面这种需求,可以考虑观察者模式:
一个对象发生变化,需要自动让多个对象知道。
例如你现在做 Qt/C++ 项目,很常见:
配置发生变化 ↓ ┌────┼────────┐ ↓ ↓ ↓ UI 日志 设备控制或者:
设备状态改变 ↓ ┌────┼─────────┐ ↓ ↓ ↓ 界面 数据记录 报警或者:
图像处理参数改变 ↓ ┌─────┼─────────┐ ↓ ↓ ↓ 图像 曲线 参数显示 刷新 刷新 刷新这种场景特别适合观察者模式。
一句话记忆:
观察者模式 = 一个对象发生变化,自动通知一群依赖它的对象。
而在 Qt 中,你可以把它直接对应成:
观察者模式
↓
Subject → signals
Observer → slots
notify → emit
attach → connect
所以如果你正在学习设计模式并且主要用 Qt/C++,观察者模式是非常值得重点掌握的一个模式。