📘 本篇从最基础的事件响应原理出发,逐步引导你掌握并实现经典的观察者模式(Observer Pattern)。我们将从概念理解、代码骨架、功能扩展、回调安全,到最终构建一个可复用的事件通知系统,帮助你在 UI 框架、组件通信、插件机制中落地使用这一核心模式。
🔁 Day 10 回顾:生命周期安全的回调机制
核心内容 | 精华总结 |
---|---|
shared_ptr | 自动管理对象释放,防止悬空访问 |
weak_ptr | 避免循环引用,回调安全解除绑定 |
std::function | 通用封装任意函数对象 |
SafeEvent 框架 | 构建支持 weak_ptr 的事件注册/触发体系 |
📌 Day 10 解决了“函数引用对象生命周期失控”问题,为本篇观察者结构打下了技术基础。
🎯 今日目标:掌握观察者模式的本质、结构与实战落地
阶段 | 学习重点 |
---|---|
原理认知 | 了解事件模型本质,明确“发布者-订阅者”的作用关系 |
架构实现 | 搭建注册/注销/通知流程,支持弱引用安全回调 |
高级特性拓展 | 添加优先级、一键解绑、支持任意参数等可扩展能力 |
实战代码封装 | 最终形成可复用的 ObserverManager 模块 |
✅ 一、从原理开始:什么是观察者模式?
📌 定义(通俗理解):
“一方发出通知,所有订阅它的人都会收到并响应”
🔄 举例:
- 公众号推送文章,粉丝自动收到通知
- 电商网站有库存变动,多个下单用户收到提醒
- 游戏中主角血量变化,UI 自动更新显示
🎯 角色组成:
角色 | 含义 |
---|---|
Subject | 发布者(被观察者):维护观察者列表,负责通知 |
Observer | 观察者:注册到 subject,等待触发时调用其方法 |
✅ 二、最小实现:支持添加/通知
🔸 基础代码骨架:
#include <iostream>
#include <vector>
#include <functional>
class EventPublisher {
public:
void addObserver(std::function<void()> cb) {
observers.push_back(cb);
}
void notifyAll() {
for (auto& cb : observers) {
cb();
}
}
private:
std::vector<std::function<void()>> observers;
};
int main() {
EventPublisher ep;
ep.addObserver([](){ std::cout << "观察者A:收到通知\n"; });
ep.addObserver([](){ std::cout << "观察者B:收到通知\n"; });
ep.notifyAll();
}
📌 输出:
观察者A:收到通知
观察者B:收到通知
✅ 三、增强功能:支持解绑 + 生命周期安全
🔸 引入 weak_ptr 管理对象安全
#include <memory>
class ObserverBase {
public:
virtual void onNotified() = 0;
virtual ~ObserverBase() = default;
};
class Subject {
public:
void add(std::shared_ptr<ObserverBase> obs) {
observers.emplace_back(obs);
}
void notifyAll() {
for (auto it = observers.begin(); it != observers.end(); ) {
if (auto sp = it->lock()) {
sp->onNotified();
++it;
} else {
it = observers.erase(it); // 自动移除已销毁的对象
}
}
}
private:
std::vector<std::weak_ptr<ObserverBase>> observers;
};
使用者实现:
class UIElement : public ObserverBase {
public:
void onNotified() override {
std::cout << "🖥 UI更新!\n";
}
};
int main() {
Subject s;
{
auto ui = std::make_shared<UIElement>();
s.add(ui);
s.notifyAll(); // 输出 🖥 UI更新!
} // ui 被释放
s.notifyAll(); // 不输出,不崩溃
}
📌 实战意义:
- 保证 UI / 控件销毁后不会崩溃
- 自动清理,避免手动解绑繁琐
✅ 四、进阶功能:支持带参数 / ID 注销 / 一次性订阅
class EventHub {
public:
using Token = int;
using Callback = std::function<void(int)>;
Token subscribe(Callback cb) {
callbacks[++idCounter] = cb;
return idCounter;
}
void unsubscribe(Token token) {
callbacks.erase(token);
}
void notify(int data) {
for (auto& [id, cb] : callbacks) {
cb(data);
}
}
private:
std::unordered_map<Token, Callback> callbacks;
Token idCounter = 0;
};
示例:
EventHub hub;
int t1 = hub.subscribe([](int d){ std::cout << "用户A收到:" << d << "\n"; });
hub.subscribe([](int d){ std::cout << "用户B收到:" << d << "\n"; });
hub.notify(100);
hub.unsubscribe(t1);
hub.notify(200);
📌 输出:
用户A收到:100
用户B收到:100
用户B收到:200
✅ 五、封装通用模板:ObserverManager
template<typename... Args>
class ObserverManager {
public:
using Callback = std::function<void(Args...)>;
using Token = size_t;
Token connect(Callback cb) {
observers[++id] = cb;
return id;
}
void disconnect(Token t) {
observers.erase(t);
}
void notify(Args... args) {
for (auto& [_, cb] : observers) {
cb(args...);
}
}
private:
std::unordered_map<Token, Callback> observers;
Token id = 0;
};
应用场景:
- UI 控件监听尺寸变化:
ObserverManager<int width, int height>
- 网络事件通知:
ObserverManager<std::string message>
✅ 六、经验总结:观察者模式设计建议
场景 | 推荐实现方法 |
---|---|
简单通知,无解绑 | vector + lambda |
多观察者,需解绑 | 使用 map + ID 管理 |
避免生命周期崩溃 | 统一使用 weak_ptr + lock |
允许观察者自己绑定自身 | 使用 enable_shared_from_this |
传参数 / 一次性解绑等拓展 | 使用 template + std::function 封装 |
📌 小结:观察者模式不止用于 UI,凡是“事件 + 订阅响应”场景,它都是最自然的解决方案。
📘 实战反思:从零构建一个清晰的事件中心
- ✅ 用最简结构实现“注册-通知”
- ✅ 引入智能指针解决生命周期问题
- ✅ 扩展 ID 管理,实现解绑机制
- ✅ 模板泛化支持任意参数与事件类型
- ✅ 总结设计思维:观察者模式 = 面向通知的编程骨架
🔭 Day 12 预告:信号-槽机制(Qt 式)设计
我们将在下一篇探索更先进的事件系统模型:
- 模拟 Qt 的 Signal/Slot 信号槽连接
- 自动绑定对象 + 参数 + 弱引用
- 一次性触发 + 多路广播 + 分组订阅
如需提前加入 UI 框架、跨线程通知、异步触发等特性,也欢迎你提出建议 ✅