观察者模式(Observer Pattern)一次性讲透,C++版,适配你Qt工控开发场景
1、一句话定义
观察者模式:一对多的依赖关系。当「主题(被观察对象)」状态发生变化时,自动通知所有「观察者」,观察者收到通知后自行做处理。
通俗例子:
主题 = 温度传感器;观察者 = 显示屏、报警模块、日志模块。
传感器温度一变,自动推消息给显示屏刷新数值、超温就触发报警、同时写入日志。传感器不需要硬编码调用显示屏/报警的函数,解耦。
两个核心角色:
- Subject 主题(被观察者)
◦ 维护观察者列表
◦ 提供接口:增加观察者、移除观察者、通知全部观察者
- Observer 观察者
◦ 统一接口 update(),主题通知时,调用这个函数更新自己
核心思想:主题只知道观察者实现了统一接口,不关心观察者具体是谁、做什么业务,实现解耦。
2、适用场景(工控Qt项目,重点)
✅适合:
-
一个数据变化,多个模块需要同步更新(比如:设备状态变更 → UI界面刷新、日志记录、告警推送、DDS上报),你做的想定编辑工具非常适合这个场景
-
事件广播,发布订阅,模块之间不想强依赖(A模块代码里不写B模块头文件)
-
运行时动态增加/删除观察者(比如插件,加载插件就注册观察者,卸载就取消注册)
❌不适合:
-
观察者数量巨大、通知频繁,性能压力高;
-
简单单次调用,没必要上这套模式,过度设计。
3、基础C++最简代码(纯C++,无Qt)
#include <iostream>
#include <vector>
#include <memory>
// 观察者抽象基类
class Observer {
public:
virtual ~Observer() = default;
// 收到通知的回调接口
virtual void update(int state) = 0;
};
// 主题(被观察者)抽象基类
class Subject {
public:
virtual ~Subject() = default;
// 注册观察者
void attach(std::shared_ptr<Observer> obs) {
observers.push_back(obs);
}
// 移除观察者
void detach(std::shared_ptr<Observer> obs) {
for(auto it = observers.begin(); it != observers.end();)
{
if(*it == obs)
it = observers.erase(it);
else
++it;
}
}
// 通知所有观察者
void notify(int newState) {
for(auto &obs : observers)
{
obs->update(newState);
}
}
private:
std::vector<std::shared_ptr<Observer>> observers;
};
// 具体主题:设备数据源
class DeviceSubject : public Subject {
private:
int deviceState = 0;
public:
void setState(int s) {
deviceState = s;
notify(s); // 状态修改,自动广播通知所有观察者
}
};
// 具体观察者1:UI界面
class UiObserver : public Observer {
public:
void update(int state) override {
std::cout << "UI刷新,设备状态:" << state << "\n";
}
};
// 具体观察者2:告警模块
class AlarmObserver : public Observer {
public:
void update(int state) override {
if(state > 80)
std::cout << "告警!设备异常!\n";
}
};
int main()
{
auto device = std::make_shared<DeviceSubject>();
auto ui = std::make_shared<UiObserver>();
auto alarm = std::make_shared<AlarmObserver>();
// 注册
device->attach(ui);
device->attach(alarm);
// 修改状态,自动通知所有观察者
device->setState(50);
device->setState(90);
// 取消注册,告警不再接收消息
device->detach(alarm);
device->setState(40);
return 0;
}
4、Qt里面的观察者模式:信号槽(重点!你工控开发天天在用)
Qt的 Signal & Slot 本质就是观察者模式!
• QObject(主题):emit信号 emit stateChanged(val)
• Slot槽函数(观察者update):收到信号执行回调
对比:
• 原生观察者:手动维护观察者vector,循环调用update
• Qt信号槽:Qt框架底层帮你管理订阅列表,线程安全版本还支持跨线程信号槽
// Qt信号槽等价于观察者
class Device : public QObject
{
Q_OBJECT
public:
void setValue(int v){
m_val = v;
emit valueChanged(m_val); // 通知所有订阅者
}
signals:
void valueChanged(int);
private:
int m_val = 0;
};
class UiWidget : public QObject
{
Q_OBJECT
public slots:
void onValueUpdate(int v){
qDebug()<<"UI更新 "<<v;
}
};
// 使用:connect 就是 attach注册;disconnect就是detach取消订阅
// connect(device, &Device::valueChanged, ui, &UiWidget::onValueUpdate);
面试高频点:Qt信号槽是观察者模式的一种实现。
5、关键坑点(工控项目踩坑重点,面试必考)
- 循环引用问题
用裸指针:忘记detach,主题销毁了,观察者还在,野指针;
shared_ptr:互相持有,内存泄漏。
✅解决:weak_ptr存观察者列表,不要全部shared_ptr。
- 通知顺序不可控
遍历vector通知,观察者执行顺序是注册顺序;如果业务强依赖先后顺序,这个模式不合适。
- 同步阻塞
默认是同步调用:notify的时候,逐个调用update,一个观察者卡很久,后面全部阻塞。
✅工控方案:异步队列(把回调丢线程池),避免一个模块卡死整个通知链路。
- 一次性通知/增量数据
默认只会推送变化,新注册的观察者拿不到当前的旧状态。
✅解决方案:attach注册的时候,主动把当前state传给新观察者。
- 多线程场景:主题状态修改+notify,观察者增删列表,必须加锁,不然vector遍历过程中增删元素直接崩溃(工控高频bug来源)。
6、面试高频问答(直接背)
Q:观察者模式解决了什么?
A:实现主题和观察者解耦,主题不需要硬编码依赖各个业务模块,新增观察者不需要修改主题代码,符合开闭原则。
Q:缺点是什么?
-
通知默认同步阻塞;
-
多线程下要处理锁;
-
观察者不知道其他观察者的存在;
-
大量观察者时通知耗时。
Q:和发布订阅(DDS)区别?
普通观察者:进程内,内存直接调用;
DDS发布订阅:跨进程/跨机器,网络层面的观察者模式,中间有数据总线。
7、一句话收尾总结
观察者 = 一对多事件广播,状态一变自动通知所有订阅模块;Qt信号槽就是它的工程化实现,DDS是跨设备的分布式观察者。