C++ · 设计模式 (组件协作=Observer<观察者模式>)
1. 概述
GoF分类
观察者模式属于行为型设计模式,定义在《设计模式:可复用面向对象软件的基础》一书中。
官方定义
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
核心目的
建立一种发布-订阅机制,让多个观察者对象能够自动响应主题对象的状态变化,同时保持两者之间的松耦合。
解决的主要问题
-
对象状态变化需要通知其他对象,但又不希望与被通知对象紧耦合
-
一个对象的改变需要同时改变其他对象,但不知道具体有多少对象需要改变
-
避免对象间频繁的轮询检查状态变化
cpp典型应用价值
-
GUI事件处理系统(如Qt信号槽机制)
-
游戏引擎中的事件系统
-
实时数据监控系统
-
股票行情通知系统
-
分布式系统中的状态同步
关键特征总结
| 特征 | 描述 |
|---|---|
| 解耦 | 主题和观察者之间松耦合 |
| 动态关系 | 观察者可动态注册和注销 |
| 广播通信 | 主题变化自动通知所有观察者 |
| 推/拉模型 | 支持两种数据传递方式 |
2. 核心思想
设计哲学
观察者模式体现了"好莱坞原则"(“Don’t call us, we’ll call you”),观察者不需要主动查询主题状态,而是由主题在状态变化时主动通知。
面向对象原则
-
开闭原则:可以新增观察者而不修改主题代码
-
松耦合原则:主题和观察者仅通过抽象接口交互
-
单一职责原则:主题负责维护状态,观察者负责响应变化
cpp特殊考虑
-
使用智能指针管理观察者生命周期
-
注意观察者注销时的线程安全问题
-
考虑异常安全,确保通知过程不会中断
-
使用现代cpp特性如lambda表达式简化回调
工作流程示意图
3. 适用场景
应该使用观察者模式的场景
- GUI事件处理
// 按钮点击事件通知多个处理器
button.attach(clickLogger);
button.attach(saveHandler);
button.click(); // 自动通知所有观察者
- 股票价格变化通知
stock.attach(new PriceAlert(100.0));
stock.attach(new PortfolioUpdater());
stock.setPrice(95.0); // 触发通知
- 游戏引擎事件系统
gameEventSystem.attach(achievementSystem);
gameEventSystem.attach(soundSystem);
player.die(); // 发送死亡事件
- 分布式系统状态同步
serverStatus.attach(loadBalancer);
serverStatus.attach(monitoringDashboard);
serverStatus.setStatus(Status::Critical);
- 数据监控仪表盘
sensor.attach(chartUpdater);
sensor.attach(logger);
sensor.setValue(42.5);
不适合使用观察者模式的场景
- 高频状态更新
-
原因:会产生大量通知影响性能
-
替代:考虑使用轮询或事件队列
- 严格顺序依赖
-
原因:观察者通知顺序不确定
-
替代:使用责任链模式
- 简单的一对一关系
-
原因:过度设计,直接回调更简单
-
替代:使用函数指针或std::function
4. UML类图
5. 结构
模式参与者详细分析
Subject(主题)
-
角色:被观察的对象
-
职责:
-
维护观察者列表
-
提供注册/注销接口
-
状态变化时通知观察者
-
ConcreteSubject(具体主题)
-
实现细节:
-
存储具体状态
-
实现状态修改逻辑
-
调用父类notify方法
-
-
特殊行为:
-
可控制何时发送通知
-
可包含通知条件判断
-
Observer(观察者)
-
角色:观察接口
-
职责:
-
定义更新接口
-
确保所有具体观察者实现一致接口
-
ConcreteObserver(具体观察者)
-
实现细节:
-
维护对主题的引用(可选)
-
实现具体响应逻辑
-
-
特殊行为:
-
可查询主题状态(拉模型)
-
可直接接收数据(推模型)
-
协作流程
-
观察者调用attach方法注册到主题
-
主题状态发生变化时调用notify
-
notify遍历观察者列表调用update
-
观察者在update中执行响应逻辑
-
观察者可随时调用detach取消注册
6. 代码示例
// Observer.h
#pragma once
#include <memory>
#include <vector>
#include <iostream>
// 前向声明
class Subject;
// 观察者接口
class Observer {
public:
virtual ~Observer() = default;
virtual void update(const Subject& subject) = 0;
};
// 主题接口
class Subject {
public:
virtual ~Subject() = default;
void attach(std::shared_ptr<Observer> observer) {
observers_.push_back(observer);
}
void detach(std::shared_ptr<Observer> observer) {
observers_.erase(
std::remove(observers_.begin(), observers_.end(), observer),
observers_.end());
}
void notify() {
for (auto& observer : observers_) {
observer->update(*this);
}
}
private:
std::vector<std::shared_ptr<Observer>> observers_;
};
// 具体主题:股票
class Stock : public Subject {
public:
Stock(std::string symbol, double price)
: symbol_(std::move(symbol)), price_(price) {}
void setPrice(double price) {
if (price_ != price) {
price_ = price;
notify(); // 价格变化时通知观察者
}
}
const std::string& getSymbol() const { return symbol_; }
double getPrice() const { return price_; }
private:
std::string symbol_;
double price_;
};
// 具体观察者:价格显示器
class PriceDisplay : public Observer {
public:
void update(const Subject& subject) override {
const auto& stock = dynamic_cast<const Stock&>(subject);
std::cout << "PriceDisplay: " << stock.getSymbol()
<< " price updated to " << stock.getPrice() << "\n";
}
};
// 具体观察者:价格警报器
class PriceAlert : public Observer {
public:
PriceAlert(double alertPrice) : alertPrice_(alertPrice) {}
void update(const Subject& subject) override {
const auto& stock = dynamic_cast<const Stock&>(subject);
if (stock.getPrice() > alertPrice_) {
std::cout << "ALERT: " << stock.getSymbol()
<< " price exceeded " << alertPrice_
<< " (current: " << stock.getPrice() << ")\n";
}
}
private:
double alertPrice_;
};
// main.cpp
#include "Observer.h"
#include <memory>
int main() {
// 创建股票对象
Stock apple("AAPL", 150.0);
// 创建观察者
auto display = std::make_shared<PriceDisplay>();
auto alert = std::make_shared<PriceAlert>(160.0);
// 注册观察者
apple.attach(display);
apple.attach(alert);
// 价格变化触发通知
apple.setPrice(155.0);
apple.setPrice(162.5);
// 注销一个观察者
apple.detach(alert);
apple.setPrice(165.0);
return 0;
}
7. 运行结果
PriceDisplay: AAPL price updated to 155
PriceDisplay: AAPL price updated to 162.5
ALERT: AAPL price exceeded 160 (current: 162.5)
PriceDisplay: AAPL price updated to 165
关键输出解释:
-
第一次价格变化到155时,只有显示器收到通知
-
价格涨到162.5时,显示器和警报器都收到通知
-
注销警报器后,165的价格变化只有显示器收到通知
8. 应用场景
实际项目中的应用案例
cpp标准库
-
std::function和std::bind可用于实现简化版观察者
-
信号槽机制的底层思想类似观察者模式
Boost库
-
Boost.Signals2库提供了线程安全的观察者实现
-
用于实现事件系统和回调机制
Qt框架
-
信号槽机制是观察者模式的扩展实现
-
广泛应用于GUI事件处理
游戏开发
-
Unity的SendMessage系统
-
游戏事件处理(如成就系统、UI更新)
系统编程
-
文件系统监控(inotify)
-
系统状态监控(CPU温度、负载等)
行业特定应用
嵌入式系统
-
传感器数据监控
-
硬件状态变化通知
-
低功耗模式下的唤醒事件
高性能计算
-
计算任务进度通知
-
分布式计算结果收集
-
资源使用监控
图形编程
-
渲染状态变化通知
-
着色器参数更新
-
场景图更新通知
网络编程
-
连接状态变化
-
数据包到达通知
-
服务质量监控
9. 变体
主要变体实现
基础变体(经典实现)
// 使用原始指针和手动管理
class BasicSubject {
std::vector<Observer*> observers_;
// ...
};
智能指针变体
// 使用shared_ptr自动管理生命周期
class SafeSubject {
std::vector<std::shared_ptr<Observer>> observers_;
// ...
};
模板变体
template<typename T>
class GenericObserver {
virtual void update(const T& subject) = 0;
};
cpp17变体(使用any)
class AnyObserver {
virtual void update(const std::any& data) = 0;
};
变体对比表格
| 变体名称 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 经典实现 | 简单直接 | 需手动管理内存 | 小型项目、性能敏感场景 |
| 智能指针 | 自动内存管理 | 轻微性能开销 | 大多数现代cpp项目 |
| 模板变体 | 类型安全 | 代码膨胀 | 需要强类型检查的场景 |
| std::any变体 | 高度灵活 | 类型不安全 | 需要处理多种事件类型的系统 |
10. 观察者模式 vs. 其他模式
与相似模式的区别
与发布-订阅模式区别
-
观察者:主题直接维护观察者列表
-
发布-订阅:通过消息代理解耦更彻底
与中介者模式区别
-
观察者:一对多直接通知
-
中介者:多对多通过中介协调
与责任链模式区别
-
观察者:所有观察者都会收到通知
-
责任链:请求被一个处理者处理后停止传递
选择指南
选择观察者模式当:
-
需要一对多的通知机制
-
希望发送者和接收者解耦
-
接收者需要动态增减
选择其他模式当:
-
需要更松散的耦合(考虑发布-订阅)
-
需要复杂的协调(考虑中介者)
-
需要请求过滤(考虑责任链)
11. 观察者模式的优缺点
优点分析
- 松耦合
-
主题和观察者仅通过抽象接口交互
-
可独立修改和扩展两者
-
在cpp中通过抽象基类实现接口分离
- 动态关系
-
运行时可以动态添加/移除观察者
-
比静态编译时绑定更灵活
-
特别适合插件架构
- 广播通信
-
一次状态变化可通知多个对象
-
避免显式调用多个对象的方法
-
简化事件处理逻辑
缺点分析
- 通知顺序不确定
-
观察者收到通知的顺序不可控
-
可能导致依赖顺序的问题
-
缓解:明确不依赖顺序或引入优先级机制
- 性能开销
-
大量观察者时通知过程耗时
-
频繁状态变化导致性能问题
-
缓解:批量通知或异步通知
12. 什么时候不要使用观察者模式?
明确的反对信号
场景1:简单回调足够
-
问题:只有一个对象需要通知
-
为什么不适用:过度设计
-
更好方案:直接使用函数指针或std::function
场景2:高频状态更新
-
问题:每秒数千次状态变化
-
为什么不适用:通知开销太大
-
更好方案:轮询或事件队列
场景3:需要保证通知顺序
-
问题:观察者之间有严格依赖顺序
-
为什么不适用:观察者模式不保证顺序
-
更好方案:责任链或显式调用链
反模式警示
- 内存泄漏
-
观察者忘记注销
-
使用weak_ptr或自动注销机制
- 链式通知
-
观察者中又修改主题状态
-
导致无限递归通知
-
应避免在update中修改主题
- 过度通知
-
不必要的小状态变化都通知
-
应设置最小变化阈值
13. 总结
核心价值总结
观察者模式是解耦对象间依赖关系的经典解决方案,特别适合状态变化通知场景。在cpp中,结合现代特性如智能指针和lambda表达式,可以实现既安全又灵活的观察者实现。
最佳实践建议
-
优先使用智能指针管理观察者生命周期
-
考虑线程安全性,特别是在多线程环境中
-
提供明确的attach/detach接口
-
考虑添加优先级机制控制通知顺序
-
对于性能敏感场景,考虑批量通知或异步通知
进阶学习路径
-
研究发布-订阅模式的分布式实现
-
学习Qt信号槽机制的实现原理
-
探索反应式编程与观察者模式的关系
-
实践项目:实现一个完整的事件系统
// 附加:线程安全的观察者模式示例
#include <mutex>
class ThreadSafeSubject : public Subject {
public:
void attach(std::shared_ptr<Observer> observer) override {
std::lock_guard<std::mutex> lock(mutex_);
Subject::attach(observer);
}
void detach(std::shared_ptr<Observer> observer) override {
std::lock_guard<std::mutex> lock(mutex_);
Subject::detach(observer);
}
void notify() override {
std::lock_guard<std::mutex> lock(mutex_);
Subject::notify();
}
private:
std::mutex mutex_;
};
更多推荐
所有评论(0)