云计算百科
云计算领域专业知识百科平台

观察者模式——C++

观察者模式(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++,观察者模式是非常值得重点掌握的一个模式。

赞(0)
未经允许不得转载:网硕互联帮助中心 » 观察者模式——C++
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!