观察者模式是 行为型设计模式 的核心成员,其核心目标是:定义对象间的一对多依赖关系,当一个对象(被观察者)的状态发生变化时,所有依赖它的对象(观察者)会自动收到通知并更新。
这种模式本质是"发布-订阅"机制------被观察者(发布者)无需知道具体的观察者(订阅者)是谁,只需在状态变化时广播通知,观察者按需订阅并响应。在 C++ 开发中,观察者模式广泛应用于事件驱动系统、UI 交互、状态同步、消息通知等场景(如 Qt 的信号槽机制、GUI 控件事件响应、分布式系统中的消息推送)。
本文将从 核心原理、结构组成、完整实现、进阶优化、工程实践 等维度,全面解析 C++ 观察者模式的设计与落地。
一、观察者模式的核心目标与设计思想
1.1 核心目标
- 解耦被观察者与观察者:被观察者无需知道观察者的具体类型和实现,仅依赖抽象接口;观察者也无需关注被观察者的内部逻辑,只需响应状态变化通知。
- 支持动态订阅/取消订阅:观察者可随时加入或退出订阅关系,被观察者无需修改代码即可适配。
- 广播通知:被观察者状态变化时,自动通知所有注册的观察者,确保状态一致性。
- 单一职责:被观察者负责维护状态和通知逻辑,观察者负责处理通知(如更新 UI、执行业务逻辑),各司其职。
1.2 核心设计思想
- 依赖倒置原则:被观察者依赖观察者的抽象接口(而非具体实现),观察者依赖被观察者的抽象接口,避免紧耦合。
- 开闭原则:新增观察者时,无需修改被观察者的代码;新增被观察者时,观察者可通过抽象接口适配,无需修改自身逻辑。
- 事件驱动:以"状态变化"为事件,触发观察者的响应,符合事件驱动编程模型。
二、观察者模式的核心结构(4大角色)
观察者模式的结构由 4 个核心角色组成,严格遵循"依赖抽象"原则:
| 角色 | 职责描述 |
|---|---|
| 抽象被观察者(Subject) | 定义被观察者的核心接口:注册观察者、取消注册、通知所有观察者。 |
| 具体被观察者(ConcreteSubject) | 实现抽象被观察者接口,维护自身状态;状态变化时,调用通知方法触发观察者更新。 |
| 抽象观察者(Observer) | 定义观察者的核心接口:接收被观察者的通知并执行更新操作(纯虚函数)。 |
| 具体观察者(ConcreteObserver) | 实现抽象观察者接口,根据被观察者的通知执行具体业务逻辑(如更新 UI、记录日志)。 |
结构示意图
┌─────────────────┐ ┌─────────────────┐
│ Subject(抽象) │ │ Observer(抽象)│
├─────────────────┤ ├─────────────────┤
│ + register(O) │ │ + update() │
│ + unregister(O) │ └─────────────────┘
│ + notify() │ ▲
└────────┬────────┘ │
│ │
▼ │
┌─────────────────┐ ┌─────────────────┐
│ ConcreteSubject │ │ ConcreteObserver│
├─────────────────┤ ├─────────────────┤
│ - state: T │ │ - name: string │
│ + getState() │ │ + update() { ...}
│ + setState() │ └─────────────────┘
└─────────────────┘
三、C++ 观察者模式的完整实现(基础版)
以"气象站(被观察者)+ 显示屏(观察者)"为例:气象站监测温度变化,当温度更新时,所有注册的显示屏自动显示最新温度。
3.1 步骤 1:定义抽象接口(Subject + Observer)
抽象接口是解耦的核心,确保被观察者和观察者仅依赖抽象,不依赖具体实现。
cpp
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
#include <mutex> // 后续线程安全用,基础版可先忽略
// 前置声明:抽象被观察者需要知道抽象观察者的存在
class Observer;
// 1. 抽象被观察者(Subject):定义核心接口
class Subject {
public:
virtual ~Subject() = default; // 虚析构,确保子类析构被调用
// 注册观察者
virtual void registerObserver(Observer* observer) = 0;
// 取消注册观察者
virtual void removeObserver(Observer* observer) = 0;
// 通知所有观察者(状态变化时调用)
virtual void notifyObservers() = 0;
};
// 2. 抽象观察者(Observer):定义更新接口
class Observer {
public:
virtual ~Observer() = default;
// 纯虚函数:接收被观察者的通知,执行更新操作
// 参数:被观察者的最新状态(此处为温度,可扩展为任意类型)
virtual void update(float temperature) = 0;
// 可选:获取观察者名称(用于调试/识别)
virtual std::string getName() const = 0;
};
3.2 步骤 2:实现具体被观察者(ConcreteSubject)
具体被观察者维护自身状态(温度),并实现注册、取消、通知逻辑。
cpp
// 3. 具体被观察者:气象站(WeatherStation)
class WeatherStation : public Subject {
public:
~WeatherStation() override {
std::cout << "气象站被销毁" << std::endl;
}
// 注册观察者:将观察者加入列表
void registerObserver(Observer* observer) override {
if (observer == nullptr) return;
// 避免重复注册
auto it = std::find(observers_.begin(), observers_.end(), observer);
if (it == observers_.end()) {
observers_.push_back(observer);
std::cout << "注册观察者:" << observer->getName() << std::endl;
}
}
// 取消注册:将观察者从列表中移除
void removeObserver(Observer* observer) override {
if (observer == nullptr) return;
auto it = std::find(observers_.begin(), observers_.end(), observer);
if (it != observers_.end()) {
std::cout << "取消注册观察者:" << observer->getName() << std::endl;
observers_.erase(it);
}
}
// 通知所有观察者:遍历列表,调用每个观察者的 update 方法
void notifyObservers() override {
// 遍历所有注册的观察者,发送最新状态
for (Observer* observer : observers_) {
observer->update(current_temperature_);
}
}
// 业务方法:更新气象站的温度(状态变化触发点)
void setTemperature(float temperature) {
if (temperature != current_temperature_) { // 仅状态变化时通知
current_temperature_ = temperature;
std::cout << "\n气象站温度更新为:" << current_temperature_ << "℃" << std::endl;
notifyObservers(); // 状态变化,通知所有观察者
}
}
// 获取当前温度(可选,供观察者主动查询)
float getTemperature() const {
return current_temperature_;
}
private:
std::vector<Observer*> observers_; // 存储注册的观察者列表
float current_temperature_ = 0.0f; // 被观察者的核心状态:当前温度
};
3.3 步骤 3:实现具体观察者(ConcreteObserver)
具体观察者实现 update 方法,根据被观察者的通知执行具体逻辑(如显示温度、记录日志)。
cpp
// 4. 具体观察者1:手机显示屏(MobileDisplay)
class MobileDisplay : public Observer {
public:
explicit MobileDisplay(std::string name) : name_(std::move(name)) {}
~MobileDisplay() override {
std::cout << "手机显示屏[" << name_ << "]被销毁" << std::endl;
}
// 实现 update 方法:接收温度更新,显示到手机屏幕
void update(float temperature) override {
std::cout << "手机显示屏[" << name_ << "]:当前温度为 " << temperature << "℃" << std::endl;
}
std::string getName() const override {
return "MobileDisplay-" + name_;
}
private:
std::string name_; // 观察者名称(如用户ID)
};
// 5. 具体观察者2:桌面显示屏(DesktopDisplay)
class DesktopDisplay : public Observer {
public:
explicit DesktopDisplay(std::string name) : name_(std::move(name)) {}
~DesktopDisplay() override {
std::cout << "桌面显示屏[" << name_ << "]被销毁" << std::endl;
}
// 实现 update 方法:接收温度更新,显示到桌面屏幕
void update(float temperature) override {
std::cout << "桌面显示屏[" << name_ << "]:检测到温度变化,当前温度 " << temperature << "℃" << std::endl;
}
std::string getName() const override {
return "DesktopDisplay-" + name_;
}
private:
std::string name_;
};
// 6. 具体观察者3:日志记录器(LogRecorder)
class LogRecorder : public Observer {
public:
explicit LogRecorder(std::string log_file) : log_file_(std::move(log_file)) {}
~LogRecorder() override {
std::cout << "日志记录器[" << log_file_ << "]被销毁" << std::endl;
}
// 实现 update 方法:记录温度变化到日志
void update(float temperature) override {
std::cout << "日志记录器[" << log_file_ << "]:记录温度 " << temperature << "℃(时间:2025-12-02)" << std::endl;
}
std::string getName() const override {
return "LogRecorder-" + log_file_;
}
private:
std::string log_file_; // 日志文件名
};
3.4 步骤 4:测试代码(验证观察者模式)
cpp
int main() {
// 1. 创建被观察者:气象站
WeatherStation weather_station;
// 2. 创建观察者:3个显示屏 + 1个日志记录器
MobileDisplay mobile1("用户A");
MobileDisplay mobile2("用户B");
DesktopDisplay desktop("办公室");
LogRecorder log_recorder("temperature.log");
// 3. 注册观察者到气象站
weather_station.registerObserver(&mobile1);
weather_station.registerObserver(&mobile2);
weather_station.registerObserver(&desktop);
weather_station.registerObserver(&log_recorder);
// 4. 气象站温度变化(触发通知)
weather_station.setTemperature(25.5f);
weather_station.setTemperature(26.0f); // 再次变化
weather_station.setTemperature(26.0f); // 温度不变,不通知
// 5. 取消某个观察者的注册
std::cout << "\n--- 取消注册用户B的手机显示屏 ---" << std::endl;
weather_station.removeObserver(&mobile2);
// 6. 温度再次变化(取消注册的观察者不再收到通知)
weather_station.setTemperature(27.3f);
return 0;
}
3.5 运行结果与分析
注册观察者:MobileDisplay-用户A
注册观察者:MobileDisplay-用户B
注册观察者:DesktopDisplay-办公室
注册观察者:LogRecorder-temperature.log
气象站温度更新为:25.5℃
手机显示屏[用户A]:当前温度为 25.5℃
手机显示屏[用户B]:当前温度为 25.5℃
桌面显示屏[办公室]:检测到温度变化,当前温度 25.5℃
日志记录器[temperature.log]:记录温度 25.5℃(时间:2025-12-02)
气象站温度更新为:26.0℃
手机显示屏[用户A]:当前温度为 26.0℃
手机显示屏[用户B]:当前温度为 26.0℃
桌面显示屏[办公室]:检测到温度变化,当前温度 26.0℃
日志记录器[temperature.log]:记录温度 26.0℃(时间:2025-12-02)
--- 取消注册用户B的手机显示屏 ---
取消注册观察者:MobileDisplay-用户B
气象站温度更新为:27.3℃
手机显示屏[用户A]:当前温度为 27.3℃
桌面显示屏[办公室]:检测到温度变化,当前温度 27.3℃
日志记录器[temperature.log]:记录温度 27.3℃(时间:2025-12-02)
日志记录器[temperature.log]被销毁
桌面显示屏[办公室]被销毁
手机显示屏[用户B]被销毁
手机显示屏[用户A]被销毁
气象站被销毁
关键结论:
- 被观察者状态变化时,所有注册的观察者自动收到通知(
update方法被调用)。 - 观察者可动态注册/取消注册,不影响被观察者和其他观察者。
- 被观察者与观察者通过抽象接口解耦:新增观察者(如
TVDisplay)时,无需修改WeatherStation代码。
四、C++ 观察者模式的核心问题与进阶优化
基础版实现满足基本需求,但在工程实践中需解决 线程安全、状态传递、内存管理、灵活性 等问题,以下是关键优化点:
4.1 优化 1:线程安全(多线程场景必备)
基础版中,observers_ 列表的读写(注册、取消、通知)若在多线程环境下执行(如一个线程更新温度,另一个线程注册观察者),会导致数据竞争(vector 非线程安全)。
解决方案:添加互斥锁(std::mutex)
cpp
class WeatherStation : public Subject {
public:
// ... 其他方法不变 ...
void registerObserver(Observer* observer) override {
std::lock_guard<std::mutex> lock(mutex_); // 加锁保护
if (observer == nullptr) return;
auto it = std::find(observers_.begin(), observers_.end(), observer);
if (it == observers_.end()) {
observers_.push_back(observer);
std::cout << "注册观察者:" << observer->getName() << std::endl;
}
}
void removeObserver(Observer* observer) override {
std::lock_guard<std::mutex> lock(mutex_); // 加锁保护
if (observer == nullptr) return;
auto it = std::find(observers_.begin(), observers_.end(), observer);
if (it != observers_.end()) {
std::cout << "取消注册观察者:" << observer->getName() << std::endl;
observers_.erase(it);
}
}
void notifyObservers() override {
std::lock_guard<std::mutex> lock(mutex_); // 加锁保护
// 复制观察者列表(避免通知过程中列表被修改,如观察者取消注册)
std::vector<Observer*> temp_observers = observers_;
lock.unlock(); // 提前解锁,减少阻塞时间
for (Observer* observer : temp_observers) {
observer->update(current_temperature_);
}
}
private:
std::vector<Observer*> observers_;
float current_temperature_ = 0.0f;
std::mutex mutex_; // 保护观察者列表的线程安全锁
};
关键优化点:
- 用
std::lock_guard自动加锁/解锁,避免死锁。 - 通知时复制观察者列表:防止通知过程中观察者取消注册导致
vector迭代器失效。
4.2 优化 2:灵活传递状态(支持多状态/任意类型)
基础版中 update 方法仅传递 temperature(单一状态),实际场景中被观察者可能有多个状态(如温度、湿度、气压),需支持灵活传递。
解决方案 1:传递被观察者指针(观察者主动查询)
观察者通过被观察者指针,按需查询所需状态,避免 update 方法参数膨胀:
cpp
// 修改抽象观察者的 update 方法
class Observer {
public:
virtual ~Observer() = default;
// 传递被观察者指针,观察者主动查询状态
virtual void update(Subject* subject) = 0;
virtual std::string getName() const = 0;
};
// 具体观察者实现(以 MobileDisplay 为例)
void MobileDisplay::update(Subject* subject) override {
// 向下转型(需确保安全,可加 dynamic_cast 校验)
WeatherStation* station = dynamic_cast<WeatherStation*>(subject);
if (station != nullptr) {
float temp = station->getTemperature();
std::cout << "手机显示屏[" << name_ << "]:当前温度为 " << temp << "℃" << std::endl;
}
}
// 被观察者通知逻辑修改
void WeatherStation::notifyObservers() override {
std::lock_guard<std::mutex> lock(mutex_);
std::vector<Observer*> temp_observers = observers_;
lock.unlock();
for (Observer* observer : temp_observers) {
observer->update(this); // 传递自身指针
}
}
解决方案 2:使用结构体/变体(传递多状态)
用 struct 封装多个状态,或用 C++17 的 std::variant 支持任意类型状态:
cpp
// 定义状态结构体
struct WeatherState {
float temperature;
float humidity;
float pressure;
};
// 抽象观察者 update 方法
virtual void update(const WeatherState& state) = 0;
// 被观察者通知时传递状态结构体
void WeatherStation::notifyObservers() override {
WeatherState state = {current_temperature_, current_humidity_, current_pressure_};
for (Observer* observer : temp_observers) {
observer->update(state);
}
}
4.3 优化 3:内存管理(避免野指针/内存泄漏)
基础版中,被观察者存储观察者的裸指针(Observer*),若观察者被销毁但未取消注册,被观察者会继续调用裸指针的 update 方法,导致野指针崩溃。
解决方案 1:使用智能指针(std::shared_ptr/std::weak_ptr)
推荐用 std::weak_ptr 存储观察者(避免循环引用),观察者通过 std::shared_ptr 管理自身生命周期:
cpp
#include <memory>
// 抽象观察者不变,具体观察者用 shared_ptr 管理
class MobileDisplay : public Observer, public std::enable_shared_from_this<MobileDisplay> {
// ... 实现不变 ...
};
// 被观察者存储 weak_ptr(不影响观察者生命周期)
class WeatherStation : public Subject {
private:
std::vector<std::weak_ptr<Observer>> observers_; // 弱指针列表
// ... 其他成员不变 ...
};
// 注册观察者(接收 shared_ptr)
void WeatherStation::registerObserver(std::shared_ptr<Observer> observer) {
if (!observer) return;
std::lock_guard<std::mutex> lock(mutex_);
observers_.push_back(observer); // weak_ptr 自动从 shared_ptr 转换
}
// 通知观察者(先锁定 weak_ptr,判断观察者是否存活)
void WeatherStation::notifyObservers() override {
std::lock_guard<std::mutex> lock(mutex_);
std::vector<std::weak_ptr<Observer>> temp_observers = observers_;
lock.unlock();
for (auto& weak_observer : temp_observers) {
// 锁定 weak_ptr,判断观察者是否还存活
if (auto observer = weak_observer.lock()) {
observer->update(this);
}
}
}
// 测试代码中创建观察者
auto mobile1 = std::make_shared<MobileDisplay>("用户A");
weather_station.registerObserver(mobile1);
关键优势:
- 观察者被销毁时,
weak_ptr会自动失效,通知时不会调用已销毁的观察者。 - 避免循环引用:被观察者用
weak_ptr引用观察者,观察者无需引用被观察者,无循环依赖。
4.4 优化 4:支持带参数的观察者(灵活响应)
实际场景中,观察者可能需要自定义响应逻辑(如"温度超过 30℃ 时才报警"),可让观察者注册时传递过滤条件或回调函数。
解决方案:观察者注册时传递回调函数
cpp
#include <functional>
// 定义回调函数类型:接收温度,返回是否处理
using UpdateCallback = std::function<void(float temperature)>;
// 抽象观察者扩展:支持回调函数
class Observer {
public:
virtual ~Observer() = default;
virtual void update(float temperature) = 0;
virtual std::string getName() const = 0;
// 可选:设置回调函数
virtual void setCallback(UpdateCallback callback) {
callback_ = std::move(callback);
}
protected:
UpdateCallback callback_;
};
// 具体观察者使用回调函数
class AlarmDisplay : public Observer {
public:
explicit AlarmDisplay(std::string name) : name_(std::move(name)) {}
void update(float temperature) override {
if (callback_) {
callback_(temperature); // 执行回调函数
}
}
std::string getName() const override {
return "AlarmDisplay-" + name_;
}
private:
std::string name_;
};
// 测试代码:注册时设置回调(温度>30℃报警)
auto alarm = std::make_shared<AlarmDisplay>("卧室");
alarm->setCallback([](float temp) {
if (temp > 30.0f) {
std::cout << "⚠️ 温度报警:当前温度 " << temp << "℃,超过阈值!" << std::endl;
}
});
weather_station.registerObserver(alarm);
// 触发报警
weather_station.setTemperature(31.2f); // 输出报警信息
4.5 优化 5:事件类型过滤(观察者仅关注特定事件)
被观察者可能产生多种事件(如"温度变化""湿度变化"),观察者可能只关注其中一种,需支持事件类型过滤。
解决方案:定义事件类型枚举,观察者注册时指定关注的事件
cpp
// 定义事件类型
enum class EventType {
TEMPERATURE_CHANGED,
HUMIDITY_CHANGED,
PRESSURE_CHANGED
};
// 抽象观察者更新方法添加事件类型参数
class Observer {
public:
virtual ~Observer() = default;
virtual void update(EventType event, const WeatherState& state) = 0;
virtual std::string getName() const = 0;
// 获取观察者关注的事件类型
virtual std::vector<EventType> getInterestedEvents() const = 0;
};
// 具体观察者(仅关注温度变化)
class TemperatureDisplay : public Observer {
public:
std::vector<EventType> getInterestedEvents() const override {
return {EventType::TEMPERATURE_CHANGED};
}
void update(EventType event, const WeatherState& state) override {
if (event == EventType::TEMPERATURE_CHANGED) {
std::cout << "温度显示屏:当前温度 " << state.temperature << "℃" << std::endl;
}
}
// ... 其他实现 ...
};
// 被观察者通知时,仅通知关注该事件的观察者
void WeatherStation::notifyObservers(EventType event) {
std::lock_guard<std::mutex> lock(mutex_);
std::vector<std::weak_ptr<Observer>> temp_observers = observers_;
lock.unlock();
WeatherState state = {current_temperature_, current_humidity_, current_pressure_};
for (auto& weak_observer : temp_observers) {
if (auto observer = weak_observer.lock()) {
// 检查观察者是否关注该事件
auto interested = observer->getInterestedEvents();
if (std::find(interested.begin(), interested.end(), event) != interested.end()) {
observer->update(event, state);
}
}
}
}
// 温度变化时,触发对应事件通知
void WeatherStation::setTemperature(float temperature) {
if (temperature != current_temperature_) {
current_temperature_ = temperature;
notifyObservers(EventType::TEMPERATURE_CHANGED); // 指定事件类型
}
}
五、观察者模式的适用场景与反场景
5.1 适用场景
- 事件驱动系统:如 GUI 框架(按钮点击、窗口关闭等事件)、游戏引擎(角色移动、碰撞检测事件)。
- 状态同步场景:如分布式系统中的数据同步(主节点状态变化,从节点自动同步)、缓存更新(数据源变化,缓存自动刷新)。
- 消息通知场景:如日志系统(核心模块状态变化,日志模块自动记录)、报警系统(监控指标异常,报警模块自动通知)。
- 解耦需求场景:被观察者和观察者需要解耦,避免紧耦合(如业务模块与通知模块分离)。
5.2 反场景(不建议使用)
- 观察者数量极少且固定:如仅 1-2 个观察者,且不会扩展,直接调用函数比观察者模式更简洁。
- 同步更新开销过大:如观察者更新逻辑复杂(耗时久),且需同步执行,会导致被观察者阻塞(可改用异步通知解决)。
- 循环依赖风险:被观察者和观察者相互引用,可能导致内存泄漏(需用弱指针等方式避免)。
- 状态变化过于频繁:如每秒上千次状态变化,观察者频繁更新会导致系统性能下降(需合并通知或节流)。
六、C++ 成熟库中的观察者模式应用
-
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); -
Boost 库 :
boost::signals2提供线程安全的观察者模式实现,支持多播回调、自动管理连接,兼容 C++11 及以上标准。 -
C++ 标准库 :无原生观察者模式实现,但可通过
std::function+std::vector快速实现轻量级观察者模式(如回调列表)。
七、总结与核心原则
观察者模式的核心是 "解耦发布者与订阅者,支持动态联动",C++ 实现的关键在于:
- 依赖抽象接口(
Subject/Observer),避免紧耦合。 - 解决线程安全、内存管理、状态传递等工程问题。
- 结合 C++ 特性(智能指针、互斥锁、变体、函数对象)提升灵活性和安全性。
核心原则
- 开闭原则优先:新增观察者/被观察者时,无需修改现有代码。
- 最小知识原则:被观察者仅通知必要信息,观察者无需了解被观察者内部逻辑。
- 线程安全不可少:多线程场景下必须加锁保护观察者列表。
- 内存安全是底线:避免裸指针野指针,优先使用智能指针管理生命周期。
在实际开发中,若需快速实现,可基于 std::function + std::vector 实现轻量级版本;若需工业级稳定性,可使用 Qt 信号槽或 Boost.Signals2;若需自定义灵活扩展,可基于本文的进阶优化方案实现。观察者模式是 C++ 事件驱动编程的基础,掌握其设计思想和工程实践,能有效提升代码的解耦度和可维护性。