设计模式入门:6. 观察者模式详解 C++实现
观察者模式详解:实现对象间的"一对多"通知,C++完整实现
引言
你有没有订阅过公众号?当你关注了一个公众号后,只要它发布了新文章,你就会自动收到推送通知。你不需要每天主动去查看公众号有没有更新,公众号会在有新内容时主动告诉你。
在软件开发中,我们也经常会遇到类似的场景:一个对象的状态发生了变化,需要通知其他多个对象做出相应的反应。比如:
- 天气预报更新后,所有显示天气的界面都要刷新
- 股票价格变动后,所有股票行情显示和交易系统都要更新
- 按钮被点击后,所有注册了点击事件的处理函数都要执行
- 配置文件修改后,所有使用该配置的模块都要重新加载
如果用传统的方式实现,我们需要让每个对象都持有其他所有需要通知的对象的引用,这会导致代码耦合度极高,难以维护和扩展。
观察者模式(Observer Pattern) 正是为了解决这个问题而生的。它是一种行为型设计模式,定义了对象之间的一对多依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都会自动收到通知并更新。
今天我们就用C++语言,从基础概念到完整实现,全面深入地理解观察者模式。
一、观察者模式的核心概念
1.1 解决的痛点
在没有观察者模式的情况下,实现对象间的通知通常有两种方式:
- 轮询方式:观察者定期主动查询主题的状态。这种方式效率极低,会浪费大量CPU资源,而且实时性差。
- 硬编码方式:主题对象持有所有观察者的引用,状态改变时逐个调用它们的更新方法。这种方式耦合度极高,新增或删除观察者都需要修改主题代码,违反开闭原则。
观察者模式采用**“发布-订阅(Publish-Subscribe)”**的思想,完美解决了这些问题。主题对象不需要知道任何观察者的存在,它只负责在状态改变时发布通知;观察者对象只需要订阅自己感兴趣的主题,当主题有更新时会自动收到通知。
1.2 核心思想
观察者模式的核心思想是:将对象分为"主题"(被观察者)和"观察者"两类。主题负责管理所有订阅了它的观察者,并在自身状态发生改变时,自动通知所有观察者。
这种方式实现了主题和观察者之间的松耦合:
- 主题不知道观察者的具体实现,只知道它们都实现了同一个观察者接口
- 观察者不知道主题的具体实现,只知道主题提供了订阅和取消订阅的接口
- 主题和观察者可以独立变化,互不影响
1.3 四个核心角色
观察者模式包含四个关键角色:
- 抽象主题(Subject):定义了主题的通用接口,声明了注册、移除和通知观察者的方法
- 具体主题(Concrete Subject):被观察的对象,维护自身的状态,当状态改变时通知所有注册的观察者
- 抽象观察者(Observer):定义了观察者的通用接口,声明了接收通知和更新的方法
- 具体观察者(Concrete Observer):实现了抽象观察者接口,在收到主题通知时更新自身的状态或执行相应的操作
二、标准观察者模式实现
2.1 UML类图

2.2 C++实现(天气站例子)
我们用最经典的"天气站"例子来实现观察者模式。假设我们有一个天气数据采集站,它会实时采集温度、湿度和气压数据。当这些数据更新时,我们需要通知多个显示设备(当前天气显示、统计显示、预报显示)更新它们的显示内容。

#include <iostream>
#include <string>
#include <vector>
#include <memory>
#include <algorithm>
// 前向声明
class Observer;
// 抽象主题:天气主题
class WeatherSubject {
public:
virtual ~WeatherSubject() = default;
// 注册观察者
virtual void attach(std::shared_ptr<Observer> observer) = 0;
// 移除观察者
virtual void detach(std::shared_ptr<Observer> observer) = 0;
// 通知所有观察者
virtual void notifyObservers() = 0;
};
// 抽象观察者
class Observer {
public:
virtual ~Observer() = default;
// 更新方法,由主题调用
virtual void update(float temperature, float humidity, float pressure) = 0;
};
// 具体主题:天气数据
class WeatherData : public WeatherSubject {
private:
std::vector<std::shared_ptr<Observer>> observers_; // 观察者列表
float temperature_; // 温度
float humidity_; // 湿度
float pressure_; // 气压
public:
// 注册观察者
void attach(std::shared_ptr<Observer> observer) override {
observers_.push_back(observer);
}
// 移除观察者
void detach(std::shared_ptr<Observer> observer) override {
auto it = std::find(observers_.begin(), observers_.end(), observer);
if (it != observers_.end()) {
observers_.erase(it);
}
}
// 通知所有观察者
void notifyObservers() override {
// 复制一份观察者列表,避免在通知过程中列表被修改导致迭代器失效
auto observers_copy = observers_;
for (const auto& observer : observers_copy) {
observer->update(temperature_, humidity_, pressure_);
}
}
// 当天气数据更新时调用
void measurementsChanged() {
notifyObservers();
}
// 设置天气数据
void setMeasurements(float temperature, float humidity, float pressure) {
temperature_ = temperature;
humidity_ = humidity;
pressure_ = pressure;
measurementsChanged();
}
// 获取天气数据(拉模式时使用)
float getTemperature() const { return temperature_; }
float getHumidity() const { return humidity_; }
float getPressure() const { return pressure_; }
};
// 具体观察者1:当前天气显示
class CurrentConditionsDisplay : public Observer {
private:
float temperature_;
float humidity_;
std::shared_ptr<WeatherData> weather_data_;
public:
explicit CurrentConditionsDisplay(std::shared_ptr<WeatherData> weather_data)
: weather_data_(weather_data) {
// 注册自己到主题
weather_data_->attach(shared_from_this());
}
// 更新显示
void update(float temperature, float humidity, float pressure) override {
temperature_ = temperature;
humidity_ = humidity;
display();
}
// 显示当前天气
void display() const {
std::cout << "当前天气: 温度 " << temperature_ << "°C, 湿度 " << humidity_ << "%" << std::endl;
}
};
// 具体观察者2:天气统计显示
class StatisticsDisplay : public Observer {
private:
float max_temp_ = -100.0f;
float min_temp_ = 100.0f;
float sum_temp_ = 0.0f;
int count_ = 0;
std::shared_ptr<WeatherData> weather_data_;
public:
explicit StatisticsDisplay(std::shared_ptr<WeatherData> weather_data)
: weather_data_(weather_data) {
weather_data_->attach(shared_from_this());
}
void update(float temperature, float humidity, float pressure) override {
sum_temp_ += temperature;
count_++;
if (temperature > max_temp_) max_temp_ = temperature;
if (temperature < min_temp_) min_temp_ = temperature;
display();
}
void display() const {
std::cout << "天气统计: 平均温度 " << sum_temp_ / count_
<< "°C, 最高温度 " << max_temp_
<< "°C, 最低温度 " << min_temp_ << "°C" << std::endl;
}
};
// 具体观察者3:天气预报显示
class ForecastDisplay : public Observer {
private:
float current_pressure_ = 1013.0f;
float last_pressure_;
std::shared_ptr<WeatherData> weather_data_;
public:
explicit ForecastDisplay(std::shared_ptr<WeatherData> weather_data)
: weather_data_(weather_data) {
weather_data_->attach(shared_from_this());
}
void update(float temperature, float humidity, float pressure) override {
last_pressure_ = current_pressure_;
current_pressure_ = pressure;
display();
}
void display() const {
std::cout << "天气预报: ";
if (current_pressure_ > last_pressure_) {
std::cout << "气压上升,天气将转晴" << std::endl;
} else if (current_pressure_ < last_pressure_) {
std::cout << "气压下降,可能会下雨" << std::endl;
} else {
std::cout << "气压稳定,天气保持不变" << std::endl;
}
}
};
// 客户端代码
int main() {
// 创建天气数据主题
auto weather_data = std::make_shared<WeatherData>();
// 创建观察者并注册到主题
auto current_display = std::make_shared<CurrentConditionsDisplay>(weather_data);
auto statistics_display = std::make_shared<StatisticsDisplay>(weather_data);
auto forecast_display = std::make_shared<ForecastDisplay>(weather_data);
std::cout << "=== 第一次天气更新 ===" << std::endl;
weather_data->setMeasurements(25.5f, 65.0f, 1012.0f);
std::cout << "\n=== 第二次天气更新 ===" << std::endl;
weather_data->setMeasurements(28.0f, 70.0f, 1010.0f);
std::cout << "\n=== 移除天气预报显示 ===" << std::endl;
weather_data->detach(forecast_display);
std::cout << "\n=== 第三次天气更新 ===" << std::endl;
weather_data->setMeasurements(26.0f, 60.0f, 1015.0f);
return 0;
}
2.3 运行结果
=== 第一次天气更新 ===
当前天气: 温度 25.5°C, 湿度 65%
天气统计: 平均温度 25.5°C, 最高温度 25.5°C, 最低温度 25.5°C
天气预报: 气压下降,可能会下雨
=== 第二次天气更新 ===
当前天气: 温度 28°C, 湿度 70%
天气统计: 平均温度 26.75°C, 最高温度 28°C, 最低温度 25.5°C
天气预报: 气压下降,可能会下雨
=== 移除天气预报显示 ===
=== 第三次天气更新 ===
当前天气: 温度 26°C, 湿度 60%
天气统计: 平均温度 26.5°C, 最高温度 28°C, 最低温度 25.5°C
2.4 代码解析
- 抽象主题
WeatherSubject:定义了attach、detach和notifyObservers三个核心方法,所有具体主题都必须实现这些方法 - 具体主题
WeatherData:维护天气数据(温度、湿度、气压),并管理观察者列表。当天气数据更新时,调用notifyObservers方法通知所有观察者 - 抽象观察者
Observer:定义了update方法,所有具体观察者都必须实现这个方法来接收通知 - 具体观察者:
CurrentConditionsDisplay、StatisticsDisplay、ForecastDisplay,它们在构造时自动注册到主题,在收到通知时更新自己的显示内容
关键细节:
- 在
notifyObservers方法中,我们先复制了一份观察者列表再进行遍历。这是为了避免在通知过程中,有观察者被移除或添加,导致迭代器失效 - 使用
std::shared_ptr来管理对象的生命周期,避免内存泄漏 - 观察者在构造时自动注册到主题,简化了客户端代码
三、推模式 vs 拉模式
观察者模式有两种不同的通知方式:推模式和拉模式,它们各有优缺点,适用于不同的场景。
3.1 推模式
推模式是指主题在通知观察者时,将所有相关的数据都推送给观察者。上面我们实现的就是推模式,update方法接收了温度、湿度、气压三个参数。
优点:
- 观察者不需要主动查询数据,使用方便
- 数据传输效率高,一次通知传递所有数据
缺点:
- 不够灵活,如果主题新增了数据字段,所有观察者的
update方法都需要修改 - 可能会传递观察者不需要的数据,造成浪费
3.2 拉模式
拉模式是指主题在通知观察者时,只告诉观察者"我的状态改变了",不传递任何数据。观察者收到通知后,主动从主题拉取自己需要的数据。
拉模式实现示意:
// 抽象观察者(拉模式)
class Observer {
public:
virtual ~Observer() = default;
// 拉模式下update方法不接收参数
virtual void update() = 0;
};
// 具体观察者(拉模式)
class CurrentConditionsDisplay : public Observer {
private:
std::shared_ptr<WeatherData> weather_data_;
public:
explicit CurrentConditionsDisplay(std::shared_ptr<WeatherData> weather_data)
: weather_data_(weather_data) {
weather_data_->attach(shared_from_this());
}
void update() override {
// 主动从主题拉取需要的数据
float temp = weather_data_->getTemperature();
float humidity = weather_data_->getHumidity();
std::cout << "当前天气: 温度 " << temp << "°C, 湿度 " << humidity << "%" << std::endl;
}
};
// 具体主题的notifyObservers方法(拉模式)
void notifyObservers() override {
auto observers_copy = observers_;
for (const auto& observer : observers_copy) {
observer->update(); // 不传递任何参数
}
}
优点:
- 非常灵活,主题新增数据字段时,不需要修改观察者的
update方法 - 观察者只拉取自己需要的数据,不会浪费资源
缺点:
- 观察者需要知道主题的接口,增加了耦合度
- 多个观察者可能会重复拉取相同的数据,降低效率
3.3 如何选择
- 如果观察者需要的数据比较固定,且所有观察者需要的数据都差不多,使用推模式
- 如果观察者需要的数据各不相同,且未来可能会新增数据字段,使用拉模式
- 在实际开发中,也可以结合两种模式的优点,主题推送一个包含所有数据的对象,观察者从中提取自己需要的数据
四、观察者模式的优缺点
4.1 优点
- 松耦合:主题和观察者之间是抽象耦合,它们只知道对方实现了对应的接口,不需要知道具体实现
- 支持广播通信:主题一次通知可以发送给所有注册的观察者,非常高效
- 符合开闭原则:新增观察者不需要修改主题代码,新增主题也不需要修改现有观察者代码
- 可以动态添加和移除观察者:在运行时可以随时改变观察者的数量和类型
- 职责单一:主题只负责管理观察者和发布通知,观察者只负责处理自己的更新逻辑
4.2 缺点
- 通知顺序不确定:观察者收到通知的顺序是不确定的,不能依赖通知顺序来编写逻辑
- 性能开销:如果观察者数量很多,通知所有观察者会有一定的性能开销
- 可能导致循环依赖:如果观察者同时也是主题,可能会导致循环通知,最终导致栈溢出
- 主题不知道更新结果:主题只负责发送通知,不知道观察者是否成功处理了通知
- 可能产生内存泄漏:如果观察者没有正确取消订阅,主题会一直持有观察者的引用,导致观察者无法被释放
五、适用场景
观察者模式特别适合以下场景:
- 事件驱动系统:如GUI系统中的按钮点击、键盘输入等事件处理
- 消息通知系统:如公众号推送、邮件通知、短信通知等
- 数据同步系统:如数据库数据变更后,同步到缓存、搜索引擎等
- 实时监控系统:如股票行情监控、服务器性能监控等
- MVC架构:Model和View之间的通信就是典型的观察者模式,Model是主题,View是观察者
经典应用案例:
- Java的
java.util.Observer和java.util.Observable(虽然已被废弃,但思想是一样的) - C#的事件和委托机制
- JavaScript的事件监听机制
- Qt的信号与槽机制
- 各种消息队列和发布-订阅系统
六、现代C++改进与变种
6.1 使用std::function和Lambda简化观察者
在C++11及以后,我们可以使用std::function和Lambda表达式来简化观察者模式的实现,不需要定义抽象观察者类:
#include <functional>
#include <vector>
// 现代C++风格的主题类
class WeatherStation {
private:
// 使用std::function作为观察者类型
using Observer = std::function<void(float, float, float)>;
std::vector<Observer> observers_;
float temperature_;
float humidity_;
float pressure_;
public:
// 注册观察者
void subscribe(Observer observer) {
observers_.push_back(std::move(observer));
}
// 通知所有观察者
void notify() {
for (const auto& observer : observers_) {
observer(temperature_, humidity_, pressure_);
}
}
// 设置天气数据
void setMeasurements(float temp, float humidity, float pressure) {
temperature_ = temp;
humidity_ = humidity;
pressure_ = pressure;
notify();
}
};
// 客户端代码
int main() {
WeatherStation station;
// 使用Lambda表达式作为观察者
station.subscribe([](float temp, float humidity, float pressure) {
std::cout << "Lambda观察者: 温度 " << temp << "°C" << std::endl;
});
station.subscribe([](float temp, float humidity, float pressure) {
std::cout << "Lambda观察者: 湿度 " << humidity << "%" << std::endl;
});
station.setMeasurements(25.0f, 60.0f, 1013.0f);
return 0;
}
这种方式非常简洁灵活,不需要定义任何观察者类,直接使用Lambda表达式作为观察者。
6.2 通用事件总线
我们可以基于观察者模式实现一个通用的事件总线,支持不同类型的事件:
#include <any>
#include <unordered_map>
#include <typeindex>
class EventBus {
private:
using Handler = std::function<void(const std::any&)>;
std::unordered_map<std::type_index, std::vector<Handler>> handlers_;
public:
// 订阅事件
template <typename EventType>
void subscribe(std::function<void(const EventType&)> handler) {
handlers_[std::type_index(typeid(EventType))].push_back(
[handler = std::move(handler)](const std::any& event) {
handler(std::any_cast<const EventType&>(event));
}
);
}
// 发布事件
template <typename EventType>
void publish(const EventType& event) {
auto it = handlers_.find(std::type_index(typeid(EventType)));
if (it != handlers_.end()) {
for (const auto& handler : it->second) {
handler(event);
}
}
}
};
// 定义事件类型
struct TemperatureChangedEvent {
float new_temperature;
};
struct HumidityChangedEvent {
float new_humidity;
};
// 客户端代码
int main() {
EventBus bus;
bus.subscribe<TemperatureChangedEvent>([](const TemperatureChangedEvent& e) {
std::cout << "温度更新: " << e.new_temperature << "°C" << std::endl;
});
bus.subscribe<HumidityChangedEvent>([](const HumidityChangedEvent& e) {
std::cout << "湿度更新: " << e.new_humidity << "%" << std::endl;
});
bus.publish(TemperatureChangedEvent{25.5f});
bus.publish(HumidityChangedEvent{65.0f});
return 0;
}
这种通用事件总线在实际项目中非常常用,可以大大降低模块之间的耦合度。
七、实际应用中的注意事项
- 避免循环依赖:如果观察者同时也是主题,一定要注意避免循环通知,否则会导致栈溢出
- 正确取消订阅:观察者在销毁前一定要取消订阅,否则主题会一直持有观察者的引用,导致内存泄漏
- 处理异常:如果某个观察者在处理通知时抛出异常,可能会影响其他观察者的处理。可以在通知时捕获异常,保证所有观察者都能收到通知
- 考虑异步通知:如果观察者的处理逻辑比较耗时,可以考虑使用异步通知,避免阻塞主题线程
- 注意线程安全:如果在多线程环境中使用观察者模式,需要对观察者列表的访问进行同步
八、总结
观察者模式是一种非常重要且常用的设计模式,它的核心思想是“发布-订阅”,实现了对象之间一对多的依赖关系,让主题和观察者之间松耦合。
在实际开发中,观察者模式无处不在。从GUI系统的事件处理,到消息队列的发布订阅,再到MVC架构的Model-View通信,都能看到观察者模式的身影。
现代C++的std::function和Lambda表达式让观察者模式的实现变得更加简洁和灵活。基于观察者模式实现的事件总线,已经成为大型项目中解耦模块的标准方式。
记住,设计模式不是银弹。只有当你确实需要实现对象间的一对多通知,并且希望主题和观察者之间松耦合时,才应该使用观察者模式。
希望这篇文章能帮助你彻底理解观察者模式,并在实际项目中正确地使用它。
更多推荐

所有评论(0)