观察者模式(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_H
class Observer
{
public:
virtual ~Observer() {}
virtual void update(float temperature) = 0;
};
#endif
这里定义观察者接口。
所有观察者都必须实现:
update()
Subject
#ifndef SUBJECT_H
#define SUBJECT_H
#include <vector>
#include "observer.h"
class Subject
{
public:
virtual ~Subject() {}
void attach(Observer *observer)
{
m_observers.push_back(observer);
}
void detach(Observer *observer)
{
for (auto it = m_observers.begin();
it != m_observers.end();
++it)
{
if (*it == observer)
{
m_observers.erase(it);
break;
}
}
}
protected:
void notify(float temperature)
{
for (Observer *observer : m_observers)
{
observer->update(temperature);
}
}
private:
std::vector<Observer *> m_observers;
};
#endif
四、实现温度传感器
#ifndef TEMPERATURESENSOR_H
#define TEMPERATURESENSOR_H
#include "subject.h"
class TemperatureSensor : public Subject
{
public:
void setTemperature(float temperature)
{
m_temperature = temperature;
// 温度发生变化
notify(m_temperature);
}
float temperature() const
{
return m_temperature;
}
private:
float m_temperature = 0;
};
#endif
重点就是:
m_temperature = temperature;
notify(m_temperature);
温度变化之后,自动通知所有观察者。
五、创建具体观察者
温度显示器
#include <iostream>
#include "observer.h"
class TemperatureDisplay : public Observer
{
public:
void update(float temperature) override
{
std::cout
<< "温度显示器:当前温度 = "
<< temperature
<< " ℃"
<< std::endl;
}
};
报警器
#include <iostream>
#include "observer.h"
class TemperatureAlarm : public Observer
{
public:
void update(float temperature) override
{
if (temperature > 30)
{
std::cout
<< "报警器:温度过高!"
<< std::endl;
}
}
};
日志记录器
#include <iostream>
#include "observer.h"
class TemperatureLogger : public Observer
{
public:
void update(float temperature) override
{
std::cout
<< "日志:记录温度 "
<< temperature
<< " ℃"
<< std::endl;
}
};
六、测试
#include "temperaturesensor.h"
int main()
{
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);
return 0;
}
运行:
温度显示器:当前温度 = 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 中写一个观察者模式实例
例如温度传感器:
class TemperatureSensor : public QObject
{
Q_OBJECT
public:
void setTemperature(float temperature)
{
if (qFuzzyCompare(m_temperature, temperature))
return;
m_temperature = temperature;
emit temperatureChanged(m_temperature);
}
signals:
void temperatureChanged(float temperature);
private:
float m_temperature = 0;
};
观察者:
class TemperatureDisplay : public QObject
{
Q_OBJECT
public slots:
void onTemperatureChanged(float temperature)
{
qDebug() << "当前温度:"
<< temperature;
}
};
连接:
TemperatureSensor sensor;
TemperatureDisplay display;
QObject::connect(
&sensor,
&TemperatureSensor::temperatureChanged,
&display,
&TemperatureDisplay::onTemperatureChanged
);
然后:
sensor.setTemperature(25);
就会自动执行:
display.onTemperatureChanged(25);
十、观察者模式和发布-订阅的区别
这两个概念非常容易混。
观察者模式
通常是:
Subject
│
├──── Observer1
├──── Observer2
└──── Observer3
Subject 直接持有 Observer。
例如:
std::vector<Observer *> m_observers;
发布-订阅
通常中间还有一个消息中心:
Publisher
│
↓
MessageBus
│
┌──┼────┐
↓ ↓ ↓
Sub1 Sub2 Sub3
Publisher 不直接知道 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++,观察者模式是非常值得重点掌握的一个模式。
网硕互联帮助中心



评论前必须登录!
注册