解决90%的状态同步问题:观察者模式推拉模型实战指南
【免费下载链接】design_patterns 图说设计模式 项目地址: https://gitcode.com/gh_mirrors/de/design_patterns
你是否曾因对象间状态同步逻辑混乱而重构代码?是否在实现消息通知时陷入"牵一发而动全身"的困境?观察者模式(Observer Pattern)通过建立松耦合的一对多依赖关系,让状态变化自动传播,完美解决这类问题。本文将深入解析观察者模式的两种实现范式——推模型与拉模型,通过图说设计模式项目的实战代码,教你如何根据业务场景选择最优设计。
观察者模式核心架构
观察者模式定义了对象间的一对多依赖关系,当观察目标(Subject)状态变化时,所有注册的观察者(Observer)将自动收到通知并更新。这种模式广泛应用于事件驱动系统、UI框架和分布式消息系统中。
经典结构四要素
- 目标(Subject):维护观察者列表,提供注册、注销和通知接口
- 具体目标(ConcreteSubject):实现目标接口,维护状态并通知观察者
- 观察者(Observer):定义更新接口,响应目标状态变化
- 具体观察者(ConcreteObserver):实现更新接口,保持与目标状态同步
核心交互流程
目标状态变化时,通过notify()方法遍历观察者列表,触发其update()方法。时序图如下:
推模型:主动推送完整数据
推模型(Push Model)中,目标主动将全部或部分状态数据推送给观察者,观察者被动接收并处理。这种模式适用于数据量小、更新频率低的场景。
推模型实现要点
- 目标主动传递数据:通知时显式传入状态参数
- 观察者接口固定:update方法参数明确,无需额外查询
- 耦合度适中:观察者需了解目标数据结构
推模型代码实现
目标接口定义 code/Obeserver/Subject.h:
class Subject {
public:
void attach(Obeserver * pObeserver);
void detach(Obeserver * pObeserver);
void notify(); // 无参数,推模型特征
virtual int getState() = 0;
virtual void setState(int i)= 0;
private:
vector<Obeserver*> m_vtObj; // 观察者列表
};
具体观察者实现 code/Obeserver/ConcreteObeserver.cpp:
void ConcreteObeserver::update(Subject * sub) {
m_obeserverState = sub->getState(); // 拉取数据,混合模式
cout << "update oberserver[" << m_objName << "] state:" << m_obeserverState << endl;
}
推模型优缺点分析
| 优点 | 缺点 |
|---|---|
| 观察者无需了解目标内部结构 | 可能推送冗余数据,浪费带宽 |
| 响应速度快,直接处理推送数据 | 接口变更影响所有观察者 |
| 实现简单直观 | 不支持按需获取数据 |
拉模型:按需获取必要数据
拉模型(Pull Model)中,目标仅通知状态变化,观察者主动向目标查询所需数据。这种模式适用于数据量大、观察者需求差异大的场景。
拉模型实现要点
- 目标仅通知变化:不传递具体数据,仅告知"发生变化"
- 观察者按需查询:通过目标提供的接口获取特定数据
- 低耦合设计:观察者可选择感兴趣的数据部分
拉模型典型应用
在行为型模式文档中,拉模型通过getState()方法实现数据获取:
// 观察者更新方法
void ConcreteObeserver::update(Subject * sub) {
// 主动拉取所需状态,而非接收全部数据
if (sub->getState() > threshold) {
m_obeserverState = sub->getState();
processCriticalState();
}
}
拉模型优缺点分析
| 优点 | 缺点 |
|---|---|
| 按需获取数据,减少传输量 | 增加一次方法调用开销 |
| 观察者自主控制数据获取 | 观察者需了解目标接口 |
| 支持差异化数据需求 | 实现相对复杂 |
推拉模型的设计选择
决策框架:四维度评估法
| 评估维度 | 推模型适用场景 | 拉模型适用场景 |
|---|---|---|
| 数据规模 | 小量固定数据 | 大量动态数据 |
| 更新频率 | 低频更新 | 高频更新 |
| 耦合要求 | 内部模块,耦合可接受 | 跨模块/系统,需低耦合 |
| 数据差异 | 观察者需求一致 | 观察者需求差异大 |
混合模型:扬长避短的实践
实际开发中常采用混合模式,目标推送关键变更类型,观察者按需拉取详细数据。如:
// 混合模型update方法
void update(Subject* sub, int changeType) {
switch(changeType) {
case STATE_CHANGED:
int detail = sub->getDetailData(); // 拉取详细数据
processStateChange(detail);
break;
// 其他变更类型处理
}
}
典型应用场景对比
| 场景 | 推荐模型 | 示例 |
|---|---|---|
| 股票行情展示 | 推模型 | 实时推送价格变动 |
| 日志系统 | 拉模型 | 按需获取级别日志 |
| UI组件更新 | 混合模型 | 推送事件类型+拉取数据 |
观察者模式的最佳实践
线程安全实现
多线程环境下需注意:
- 使用互斥锁保护观察者列表 code/Obeserver/Subject.cpp
- 避免在update方法中修改观察者列表
- 考虑使用读写锁提高并发性能
内存管理要点
- 目标析构前需 detach 所有观察者
- 使用智能指针管理观察者生命周期
- 避免循环引用(推荐弱引用+强引用结合)
性能优化策略
- 批量通知:累积多次变更后一次性通知
- 层级通知:建立观察者树状结构,分级传播
- 事件过滤:观察者注册感兴趣的事件类型
模式扩展与实战应用
MVC架构中的观察者模式
MVC架构中,模型(Model)作为目标,视图(View)作为观察者,完美体现观察者模式:
- 模型状态变化时自动通知视图更新
- 支持多视图展示同一模型数据
- 控制器(Controller)协调两者交互
框架应用实例
JDK中的java.util.Observable类和Observer接口实现了观察者模式,Android的LiveData、Vue的响应式系统也基于此模式。
运行效果展示
观察者模式示例程序运行结果:
总结与选型建议
观察者模式通过解耦状态变化与响应逻辑,显著提升系统灵活性和可扩展性。选择推模型还是拉模型,需根据数据特性、更新频率和耦合要求综合判断:
- 小数据高频更新:优先推模型
- 大数据按需获取:优先拉模型
- 跨系统集成:推荐拉模型降低耦合
- 内部模块通信:推模型实现更简洁
完整代码实现可参考项目 code/Obeserver/ 目录,更多设计模式解析见 structural_patterns/ 和 behavioral_patterns/ 文档。
你在项目中使用过观察者模式吗?欢迎在评论区分享你的推拉模型实践经验!关注项目获取更多设计模式实战指南。
【免费下载链接】design_patterns 图说设计模式 项目地址: https://gitcode.com/gh_mirrors/de/design_patterns
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考






