EventBus事件总线(观察者模式):https://blog.csdn.net/Mason___/article/details/157769142?spm=1001.2014.3001.5501
C++观察者模式的核心目的是解耦对象间的依赖关系。
其核心****目标是:定义对象间的一对多依赖关系,当一个对象(被观察者)的状态发生变化时,所有依赖它的对象(观察者)会自动收到通知并更新。
一、观察者模式的核心目标与设计思想
1.1 核心目标
- 解耦被观察者与观察者:被观察者无需知道观察者的具体类型和实现,仅依赖抽象接口;观察者也无需关注被观察者的内部逻辑,只需响应状态变化通知。
- 支持动态订阅/取消订阅:观察者可随时加入或退出订阅关系,被观察者无需修改代码即可适配。
- 广播通知:被观察者状态变化时,自动通知所有注册的观察者,确保状态一致性。
- 单一职责:被观察者负责维护状态和通知逻辑,观察者负责处理通知(如更新 UI、执行业务逻辑),各司其职。
1.2 核心设计思想
- 依赖倒置原则:被观察者依赖观察者的抽象接口(而非具体实现),观察者依赖被观察者的抽象接口,避免紧耦合。
- 开闭原则:新增观察者时,无需修改被观察者的代码;新增被观察者时,观察者可通过抽象接口适配,无需修改自身逻辑。
- 事件驱动:以"状态变化"为事件,触发观察者的响应,符合事件驱动编程模型。
二、观察者模式的核心结构(4大角色)
观察者模式的结构由 4 个核心角色组成,严格遵循"依赖抽象"原则:
| 角色 | 职责描述 |
|---|---|
| 抽象被观察者(Subject) | 定义被观察者的核心接口:注册观察者、取消注册、通知所有观察者。 |
| 具体被观察者(ConcreteSubject) | 实现抽象被观察者接口,维护自身状态;状态变化时,调用通知方法触发观察者更新。 |
| 抽象观察者(Observer) | 定义观察者的核心接口:接收被观察者的通知并执行更新操作(纯虚函数)。 |
| 具体观察者(ConcreteObserver) | 实现抽象观察者接口,根据被观察者的通知执行具体业务逻辑(如更新 UI、记录日志)。 |
三、代码示例
cpp
#include <iostream>
#include <vector>
#include <memory>
#include <algorithm>
// ==================== 观察者接口 ====================
class Observer {
public:
virtual ~Observer() = default;
// 更新方法 - 主题状态改变时被调用
virtual void update(int state) = 0;
// 可选:获取观察者标识
virtual std::string getName() const = 0;
};
// ==================== 主题接口 ====================
class Subject {
public:
virtual ~Subject() = default;
// 注册观察者
virtual void attach(std::shared_ptr<Observer> observer) = 0;
// 移除观察者
virtual void detach(std::shared_ptr<Observer> observer) = 0;
// 通知所有观察者
virtual void notify() = 0;
};
// ==================== 具体主题实现 ====================
class ConcreteSubject : public Subject {
public:
void attach(std::shared_ptr<Observer> observer) override {
observers_.push_back(observer);
std::cout << "添加观察者: " << observer->getName() << std::endl;
}
void detach(std::shared_ptr<Observer> observer) override {
auto it = std::find(observers_.begin(), observers_.end(), observer);
if (it != observers_.end()) {
observers_.erase(it);
std::cout << "移除观察者: " << observer->getName() << std::endl;
}
}
void notify() override {
std::cout << "\n=== 通知所有观察者 ===" << std::endl;
for (auto& observer : observers_) {
if (observer) {
observer->update(state_);
}
}
}
// 业务方法:改变状态
void setState(int state) {
std::cout << "\n主题状态改变: " << state_ << " -> " << state << std::endl;
state_ = state;
notify(); // 状态改变后自动通知
}
int getState() const {
return state_;
}
private:
std::vector<std::shared_ptr<Observer>> observers_;
int state_ = 0;
};
// ==================== 具体观察者实现 ====================
class ConcreteObserver : public Observer {
public:
ConcreteObserver(std::string name) : name_(std::move(name)) {}
void update(int state) override {
std::cout << "观察者 [" << name_ << "] 收到更新,新状态: " << state << std::endl;
}
std::string getName() const override {
return name_;
}
private:
std::string name_;
};
// ==================== 使用示例 ====================
int main() {
// 创建主题
auto subject = std::make_unique<ConcreteSubject>();
// 创建观察者
auto observer1 = std::make_shared<ConcreteObserver>("Observer-1");
auto observer2 = std::make_shared<ConcreteObserver>("Observer-2");
auto observer3 = std::make_shared<ConcreteObserver>("Observer-3");
// 注册观察者
subject->attach(observer1);
subject->attach(observer2);
subject->attach(observer3);
// 改变状态(自动通知)
subject->setState(100);
// 移除一个观察者
subject->detach(observer2);
// 再次改变状态
subject->setState(200);
return 0;
}
输出:
cpp
添加观察者: Observer-1
添加观察者: Observer-2
添加观察者: Observer-3
主题状态改变: 0 -> 100
=== 通知所有观察者 ===
观察者 [Observer-1] 收到更新,新状态: 100
观察者 [Observer-2] 收到更新,新状态: 100
观察者 [Observer-3] 收到更新,新状态: 100
移除观察者: Observer-2
主题状态改变: 100 -> 200
=== 通知所有观察者 ===
观察者 [Observer-1] 收到更新,新状态: 200
观察者 [Observer-3] 收到更新,新状态: 200
四、观察者模式的适用场景与反场景
4.1 适用场景
- 事件驱动系统:如 GUI 框架(按钮点击、窗口关闭等事件)、游戏引擎(角色移动、碰撞检测事件)。
- 状态同步场景:如分布式系统中的数据同步(主节点状态变化,从节点自动同步)、缓存更新(数据源变化,缓存自动刷新)。
- 消息通知场景:如日志系统(核心模块状态变化,日志模块自动记录)、报警系统(监控指标异常,报警模块自动通知)。
- 解耦需求场景:被观察者和观察者需要解耦,避免紧耦合(如业务模块与通知模块分离)。
4.2 反场景(不建议使用)
- 观察者数量极少且固定:如仅 1-2 个观察者,且不会扩展,直接调用函数比观察者模式更简洁。
- 同步更新开销过大:如观察者更新逻辑复杂(耗时久),且需同步执行,会导致被观察者阻塞(可改用异步通知解决)。
- 循环依赖风险:被观察者和观察者相互引用,可能导致内存泄漏(需用弱指针等方式避免)。
- 状态变化过于频繁:如每秒上千次状态变化,观察者频繁更新会导致系统性能下降(需合并通知或节流)。
五、C++ 成熟库中的观察者模式应用
1、 Qt 框架 :QObject + 信号槽(signals/slots)是观察者模式的经典实现,支持跨线程通知、自动连接/断开,是 Qt 的核心机制。
cpp
// Qt 信号槽示例(本质是观察者模式)
class WeatherStation : public QObject {
Q_OBJECT
signals:
void temperatureChanged(float temperature); // 信号(被观察者通知)
public:
void setTemperature(float temp) {
emit temperatureChanged(temp); // 触发信号
}
};
class Display : public QObject {
Q_OBJECT
public slots:
void onTemperatureChanged(float temp) { // 槽函数(观察者更新)
qDebug() << "当前温度:" << temp;
}
};
// 连接信号槽(注册观察者)
WeatherStation station;
Display display;
QObject::connect(&station, &WeatherStation::temperatureChanged, &display, &Display::onTemperatureChanged);
2、Boost 库 :boost::signals2 提供线程安全的观察者模式实现,支持多播回调、自动管理连接,兼容 C++11 及以上标准。
3、C++ 标准库 :无原生观察者模式实现,但可通过 std::function + std::vector 快速实现轻量级观察者模式(如回调列表)。
六、总结与核心原则
观察者模式的核心是 "解耦发布者与订阅者,支持动态联动",C++ 实现的关键在于:
- 依赖抽象接口(
Subject/Observer),避免紧耦合。 - 解决线程安全、内存管理、状态传递等工程问题。
- 结合 C++ 特性(智能指针、互斥锁、变体、函数对象)提升灵活性和安全性。
核心原则
- 开闭原则优先:新增观察者/被观察者时,无需修改现有代码。
- 最小知识原则:被观察者仅通知必要信息,观察者无需了解被观察者内部逻辑。
- 线程安全不可少:多线程场景下必须加锁保护观察者列表。
- 内存安全是底线:避免裸指针野指针,优先使用智能指针管理生命周期。
**推模式 vs 拉模式 :**推模式直接传递数据,拉模式传递通知让观察者自己获取