目录

class 卑微码农:
def __init__(self):
self.技能 = ['能读懂十年前祖传代码', '擅长用Ctrl+C/V搭建世界', '信奉"能跑就别动"的玄学']
self.发量 = 100 # 初始发量
self.咖啡因耐受度 = '极限'
def 修Bug(self, bug):
try:
# 试图用玄学解决问题
if bug.严重程度 == '离谱':
print("这一定是环境问题!")
else:
print("让我看看是谁又没写注释...哦,是我自己。")
except Exception as e:
# 如果try块都救不了,那就...
print("重启一下试试?")
self.发量 -= 1 # 每解决一个bug,头发-1
# 实例化一个我
我 = 卑微码农()
前言

一个典型的“祖传代码”问题:一个天气数据采集模块,直接硬编码调用了十几个显示模块的更新函数。每次新增一个手机端显示功能,都要修改天气数据的核心代码;更要命的是,一旦某个显示模块出问题抛异常,整个数据采集流程都会崩溃。
相信不少开发者都遇到过类似的“牵一发而动全身”的困境。而解决这种对象间一对多依赖问题的“利器”,就是今天我们要深入剖析的——观察者模式。本文会从实际开发场景出发,用通俗的语言讲透原理,再通过完整的C++代码示例,带大家从基础实现走到进阶优化,真正把设计模式用到项目里。
一、先搞懂:观察者模式到底解决什么问题?

在讲定义之前,我们先看一个生活中的例子:你订阅了《程序员》杂志,只要新刊出版,杂志社就会自动把杂志寄给你;如果你不想订了,取消订阅后就不会再收到。这里面,杂志社是“被观察者”,你是“观察者”,订阅和取消订阅就是两者的交互方式。
对应到程序世界,观察者模式的核心场景是:当一个对象(被观察者/主题)的状态发生变化时,所有依赖它的对象(观察者)都能自动收到通知并做出响应。它要解决的核心问题有两个:
-
解耦依赖:被观察者不需要知道具体有哪些观察者,只需要和观察者的“抽象接口”打交道,避免硬编码依赖。就像杂志社不需要知道每个订阅者的具体信息,只需要有收件地址列表就行。
-
灵活扩展:新增观察者时,不需要修改被观察者的代码,符合“开闭原则”(对扩展开放,对修改关闭)。就像新订阅杂志的人,不需要杂志社改造印刷流程一样。
再回到开头的天气系统问题,用观察者模式改造后,结构会变成这样:天气数据模块(被观察者)只维护一个观察者接口列表,当数据更新时,遍历列表通知所有观察者;手机端、PC端、仪表盘等显示模块(观察者),只需实现观察者接口并注册到天气模块,就能接收更新通知。新增显示模块时,完全不用动天气数据的核心代码。
二、核心结构:观察者模式的4个关键角色

根据GOF(设计模式的四位作者)的定义,观察者模式包含4个核心角色,这是我们实现的基础框架,务必搞清楚每个角色的职责:
-
抽象主题(Subject):也就是被观察者的抽象接口。定义了观察者的注册、移除和通知方法,是连接被观察者和观察者的“桥梁”。
-
具体主题(Concrete Subject):抽象主题的实现类。维护自身的状态,当状态变化时,调用通知方法触发所有注册的观察者更新。比如我们例子中的天气数据模块。
-
抽象观察者(Observer):观察者的抽象接口。定义了一个“更新”方法,当收到被观察者的通知时,就会执行这个方法。所有观察者都要实现这个接口。
-
具体观察者(Concrete Observer):抽象观察者的实现类。实现更新方法的具体逻辑,比如显示天气数据、发送短信提醒等。比如手机端显示模块、仪表盘模块。
这里的核心设计思路是“面向接口编程”:抽象主题依赖抽象观察者,而不是具体观察者。这种依赖倒置的设计,正是解耦的关键。
三、手把手实现:基于C++的天气系统案例
理论讲完,我们直接上代码。还是以天气监测系统为案例,用C++实现一个完整的观察者模式。我们会分步骤来,从基础版本到优化版本,让大家看到代码演进的过程。

3.1 第一步:定义抽象接口(核心骨架)
首先实现抽象主题和抽象观察者的接口。抽象观察者只需要一个更新方法,抽象主题则需要注册、移除和通知三个核心方法。
#include <iostream>
#include <vector>
#include <memory> // 后续用于智能指针优化
#include <algorithm> // 用于vector的remove操作
// 抽象观察者接口:所有观察者都要实现这个接口
class IObserver {
public:
// 纯虚函数:更新方法,接收天气数据
virtual void update(float temperature, float humidity, float pressure) = 0;
// 虚析构:确保子类对象能被正确释放,避免内存泄漏
virtual ~IObserver() = default;
};
// 抽象主题接口:被观察者的抽象
class ISubject {
public:
// 注册观察者
virtual void registerObserver(std::shared_ptr<IObserver> observer) = 0;
// 移除观察者
virtual void removeObserver(std::shared_ptr<IObserver> observer) = 0;
// 通知所有观察者
virtual void notifyObservers() = 0;
virtual ~ISubject() = default;
};
这里有两个关键点需要注意:
-
抽象接口的析构函数必须是虚函数,否则当用基类指针指向子类对象时,delete会只调用基类析构,导致子类资源泄漏。C++11的default关键字可以简化默认析构的写法。
-
注册方法中用了std::shared_ptr智能指针,这是现代C++的推荐做法,能自动管理内存,避免野指针问题。后面我们会详细说内存管理的坑。
3.2 第二步:实现具体主题(天气数据模块)
具体主题需要维护自身的状态(温度、湿度、气压)和观察者列表,当状态更新时,调用notifyObservers方法通知所有观察者。
// 具体主题:天气数据模块
class WeatherData : public ISubject {
private:
// 天气数据状态
float temperature_; // 温度(华氏度)
float humidity_; // 湿度(百分比)
float pressure_; // 气压(千帕)
// 观察者列表:存储抽象观察者的智能指针
std::vector<std::shared_ptr<IObserver>> observers_;
// 私有方法:检查数据是否变化(实际项目中可加更复杂的判断)
bool isDataChanged(float new_temp, float new_humidity, float new_pressure) const {
return !(abs(new_temp - temperature_) < 0.01f
&& abs(new_humidity - humidity_) < 0.01f
&& abs(new_pressure - pressure_) < 0.01f);
}
public:
// 注册观察者:将观察者添加到列表
void registerObserver(std::shared_ptr<IObserver> observer) override {
if (observer) {
observers_.push_back(observer);
std::cout << "成功注册观察者" << std::endl;
}
}
// 移除观察者:从列表中删除指定观察者
void removeObserver(std::shared_ptr<IObserver> observer) override {
if (!observer) return;
// 利用erase-remove惯用法删除元素
auto it = std::remove(observers_.begin(), observers_.end(), observer);
if (it != observers_.end()) {
observers_.erase(it);
std::cout << "成功移除观察者" << std::endl;
} else {
std::cout << "未找到该观察者" << std::endl;
}
}
// 通知所有观察者:遍历列表调用update方法
void notifyObservers() override {
std::cout << "开始通知所有观察者,当前观察者数量:" << observers_.size() << std::endl;
for (auto& observer : observers_) {
// 智能指针自动判空,避免野指针调用
if (observer) {
observer->update(temperature_, humidity_, pressure_);
}
}
}
// 模拟天气数据更新(实际项目中可能是传感器数据或网络请求)
void setWeatherData(float new_temp, float new_humidity, float new_pressure) {
std::cout << "\n检测到天气数据变化" << std::endl;
// 只有数据真的变化时才通知观察者,避免无效更新
if (isDataChanged(new_temp, new_humidity, new_pressure)) {
this->temperature_ = new_temp;
this->humidity_ = new_humidity;
this->pressure_ = new_pressure;
notifyObservers(); // 数据变化,触发通知
} else {
std::cout << "数据无实质变化,不通知观察者" << std::endl;
}
}
// 调试用:获取当前天气数据
void getCurrentData() const {
std::cout << "当前天气数据:温度" << temperature_ << "F,湿度" << humidity_
<< "%,气压" << pressure_ << "kPa" << std::endl;
}
};
这个具体主题的实现里,有几个实用的细节值得借鉴:
-
数据变化校验:增加isDataChanged方法,避免重复数据触发不必要的通知,这在高并发场景下能提升性能。
-
erase-remove惯用法:vector删除元素时,直接用erase结合remove,比遍历删除更高效且安全,这是C++容器操作的常用技巧。
-
智能指针安全调用:遍历观察者列表时,虽然用了shared_ptr,但仍保留了判空逻辑,应对极端情况下的指针失效问题。
3.3 第三步:实现具体观察者(显示模块)
我们实现三个不同的具体观察者:当前天气显示器、统计天气显示器、预警显示器,分别对应不同的业务需求,展示观察者模式的扩展性。
// 具体观察者1:当前天气显示器
class CurrentConditionsDisplay : public IObserver {
private:
float temperature_;
float humidity_;
// 持有主题的引用(可选,用于需要反向操作的场景)
ISubject& subject_;
public:
// 构造函数:注册自身到主题
CurrentConditionsDisplay(ISubject& subject) : subject_(subject) {
subject_.registerObserver(std::shared_ptr<IObserver>(this));
}
// 实现更新方法:更新数据并显示
void update(float temperature, float humidity, float pressure) override {
this->temperature_ = temperature;
this->humidity_ = humidity;
display();
}
// 显示当前天气
void display() const {
std::cout << "\n【当前天气显示器】" << std::endl;
std::cout << "当前温度:" << temperature_ << "华氏度" << std::endl;
std::cout << "当前湿度:" << humidity_ << "%" << std::endl;
}
// 可选:取消订阅
void unsubscribe() {
subject_.removeObserver(std::shared_ptr<IObserver>(this));
}
};
// 具体观察者2:统计天气显示器(记录温度极值和平均值)
class StatisticsDisplay : public IObserver {
private:
std::vector<float> temperatures_; // 存储历史温度
float max_temp_; // 最高温度
float min_temp_; // 最低温度
float avg_temp_; // 平均温度
ISubject& subject_;
public:
StatisticsDisplay(ISubject& subject) : subject_(subject), max_temp_(-1000), min_temp_(1000), avg_temp_(0) {
subject_.registerObserver(std::shared_ptr<IObserver>(this));
}
void update(float temperature, float humidity, float pressure) override {
// 更新温度记录
temperatures_.push_back(temperature);
// 计算统计数据
max_temp_ = std::max(max_temp_, temperature);
min_temp_ = std::min(min_temp_, temperature);
avg_temp_ = calculateAverage();
// 显示统计结果
display();
}
// 计算平均温度
float calculateAverage() const {
if (temperatures_.empty()) return 0;
float sum = 0;
for (float temp : temperatures_) {
sum += temp;
}
return sum / temperatures_.size();
}
void display() const {
std::cout << "\n【统计天气显示器】" << std::endl;
std::cout << "温度统计(历史" << temperatures_.size() << "条数据)" << std::endl;
std::cout << "最高温度:" << max_temp_ << "F" << std::endl;
std::cout << "最低温度:" << min_temp_ << "F" << std::endl;
std::cout << "平均温度:" << avg_temp_ << "F" << std::endl;
}
void unsubscribe() {
subject_.removeObserver(std::shared_ptr<IObserver>(this));
}
};
// 具体观察者3:天气预警显示器(当数据超出阈值时触发预警)
class AlertDisplay : public IObserver {
private:
float pressure_; // 气压(预警主要看气压变化)
const float HIGH_PRESSURE_THRESHOLD = 103.0f; // 高气压阈值
const float LOW_PRESSURE_THRESHOLD = 98.0f; // 低气压阈值
ISubject& subject_;
public:
AlertDisplay(ISubject& subject) : subject_(subject), pressure_(0) {
subject_.registerObserver(std::shared_ptr<IObserver>(this));
}
void update(float temperature, float humidity, float pressure) override {
this->pressure_ = pressure;
display();
}
void display() const {
std::cout << "\n【天气预警显示器】" << std::endl;
if (pressure_ > HIGH_PRESSURE_THRESHOLD) {
std::cout << "⚠️ 预警:高气压,可能出现晴热天气,注意防暑!" << std::endl;
} else if (pressure_ < LOW_PRESSURE_THRESHOLD) {
std::cout << "⚠️ 预警:低气压,可能出现降雨天气,注意带伞!" << std::endl;
} else {
std::cout << "✅ 当前气压正常,天气稳定" << std::endl;
}
}
void unsubscribe() {
subject_.removeObserver(std::shared_ptr<IObserver>(this));
}
};
这三个观察者的实现体现了观察者模式的核心优势:
-
职责单一:每个观察者只负责自己的业务逻辑,当前天气只显示实时数据,统计器只做数据计算,预警器只判断阈值,符合“单一职责原则”。
-
自动注册:在构造函数中自动向主题注册,简化了客户端的使用流程;同时提供unsubscribe方法,支持主动取消订阅。
-
接口统一:虽然业务不同,但都通过实现update方法接收通知,主题无需关心每个观察者的具体逻辑。
3.4 第四步:客户端测试代码(模拟实际运行)
客户端代码负责创建主题和观察者对象,模拟天气数据的变化,验证整个系统的运行效果。
// 主函数:测试观察者模式
int main() {
// 1. 创建被观察者:天气数据模块
WeatherData weather_data;
// 2. 创建观察者:自动注册到天气数据模块
CurrentConditionsDisplay current_display(weather_data);
StatisticsDisplay stats_display(weather_data);
AlertDisplay alert_display(weather_data);
// 3. 模拟第一次天气数据更新
std::cout << "=== 第一次数据更新 ===" << std::endl;
weather_data.setWeatherData(72.5f, 65.2f, 101.3f);
// 4. 模拟第二次数据更新(温度变化,湿度气压不变)
std::cout << "\n=== 第二次数据更新 ===" << std::endl;
weather_data.setWeatherData(75.8f, 65.2f, 101.3f);
// 5. 移除统计显示器,模拟用户关闭该功能
std::cout << "\n=== 移除统计天气显示器 ===" << std::endl;
stats_display.unsubscribe();
// 6. 模拟第三次数据更新(气压低于阈值)
std::cout << "\n=== 第三次数据更新 ===" << std::endl;
weather_data.setWeatherData(73.1f, 70.5f, 97.8f);
// 7. 再次添加统计显示器
std::cout << "\n=== 重新注册统计天气显示器 ===" << std::endl;
stats_display = StatisticsDisplay(weather_data); // 重新构造触发注册
// 8. 模拟第四次数据更新(气压高于阈值)
std::cout << "\n=== 第四次数据更新 ===" << std::endl;
weather_data.setWeatherData(82.3f, 50.1f, 103.5f);
return 0;
}
我们来看看运行结果(为了清晰,保留关键输出):
成功注册观察者
成功注册观察者
成功注册观察者
=== 第一次数据更新 ===
检测到天气数据变化
开始通知所有观察者,当前观察者数量:3
【当前天气显示器】
当前温度:72.5华氏度
当前湿度:65.2%
【统计天气显示器】
温度统计(历史1条数据)
最高温度:72.5F
最低温度:72.5F
平均温度:72.5F
【天气预警显示器】
✅ 当前气压正常,天气稳定
=== 第二次数据更新 ===
检测到天气数据变化
开始通知所有观察者,当前观察者数量:3
...(统计显示器的历史数据变为2条,温度更新为75.8F)...
=== 移除统计天气显示器 ===
成功移除观察者
=== 第三次数据更新 ===
检测到天气数据变化
开始通知所有观察者,当前观察者数量:2
...(此时统计显示器不再收到通知,预警显示器触发低气压预警)...
=== 重新注册统计天气显示器 ===
成功注册观察者
=== 第四次数据更新 ===
检测到天气数据变化
开始通知所有观察者,当前观察者数量:3
...(统计显示器重新接收数据,预警显示器触发高气压预警)...
从运行结果可以看出,整个系统完全符合预期:观察者能自动接收通知,移除后不再收到消息,重新注册后恢复接收,完美体现了观察者模式的灵活性。
四、进阶优化:解决实际开发中的坑
上面的基础版本已经能满足大部分场景,但在实际项目中,还有几个关键问题需要解决。这部分是进阶重点,也是区分“会用”和“用得好”的关键。

4.1 内存管理:避免野指针和重复释放
C++开发中,内存问题是永恒的痛点。在观察者模式中,最容易出现的问题是:观察者对象被销毁后,被观察者的列表中还保留着它的指针,导致后续调用时出现野指针崩溃。
我们基础版本中用了std::shared_ptr智能指针,这已经解决了大部分问题。但这里有个细节需要注意:观察者的构造函数中,用this指针构造shared_ptr时,可能会导致重复释放。因为如果客户端用栈上的对象创建观察者,shared_ptr会尝试释放栈内存,引发崩溃。
解决这个问题的最佳实践是:观察者对象也用智能指针管理,避免栈上创建。修改客户端代码如下:
int main() {
WeatherData weather_data;
// 用shared_ptr管理观察者对象,避免栈内存问题
auto current_display = std::make_shared<CurrentConditionsDisplay>(weather_data);
auto stats_display = std::make_shared<StatisticsDisplay>(weather_data);
auto alert_display = std::make_shared<AlertDisplay>(weather_data);
// 后续逻辑不变...
return 0;
}
同时,修改观察者的构造函数,不再用this指针构造shared_ptr,而是让客户端将智能指针传递给主题注册。这里可以引入“弱指针”std::weak_ptr来优化,避免循环引用:
// 优化抽象主题的注册方法,接收weak_ptr
class ISubject {
public:
virtual void registerObserver(std::weak_ptr<IObserver> observer) = 0;
virtual void removeObserver(std::weak_ptr<IObserver> observer) = 0;
// ...其他方法不变
};
// 优化WeatherData的观察者列表和注册方法
class WeatherData : public ISubject {
private:
std::vector<std::weak_ptr<IObserver>> observers_; // 改用weak_ptr
public:
void registerObserver(std::weak_ptr<IObserver> observer) override {
if (!observer.expired()) {
observers_.push_back(observer);
}
}
void notifyObservers() override {
for (auto& weak_observer : observers_) {
// 锁定weak_ptr,获取shared_ptr,判断观察者是否还存在
if (auto observer = weak_observer.lock()) {
observer->update(temperature_, humidity_, pressure_);
}
}
}
// ...其他方法相应修改
};
std::weak_ptr的核心作用是“弱引用”,它不增加引用计数,不会阻止对象被销毁。通过lock()方法可以获取到对应的shared_ptr,如果对象已销毁,lock()会返回空指针,从而完美避免野指针问题。
4.2 线程安全:多线程环境下的同步
如果被观察者和观察者在不同线程中运行(比如天气数据在采集线程更新,显示器在UI线程刷新),直接使用上面的代码会出现线程安全问题:观察者列表在被遍历的同时可能被修改(注册/移除观察者),导致迭代器失效。
解决方法是使用互斥锁(std::mutex)保护观察者列表的访问。修改WeatherData类,添加互斥锁:
#include <mutex> // 引入互斥锁头文件
class WeatherData : public ISubject {
private:
std::vector<std::weak_ptr<IObserver>> observers_;
std::mutex mutex_; // 互斥锁,保护观察者列表
public:
void registerObserver(std::weak_ptr<IObserver> observer) override {
std::lock_guard<std::mutex> lock(mutex_); // 自动加锁和解锁
if (!observer.expired()) {
observers_.push_back(observer);
}
}
void removeObserver(std::weak_ptr<IObserver> observer) override {
std::lock_guard<std::mutex> lock(mutex_);
// 移除逻辑...
}
void notifyObservers() override {
std::lock_guard<std::mutex> lock(mutex_);
// 通知逻辑...
}
};
std::lock_guard是RAII风格的锁,构造时加锁,析构时自动解锁,能避免忘记解锁导致的死锁问题。如果需要更灵活的锁控制(比如手动加锁解锁),可以用std::unique_lock。
4.3 异常处理:防止单个观察者崩溃影响全局
基础版本中,如果某个观察者的update方法抛出异常,会中断整个通知流程,导致后续观察者无法收到通知。比如预警显示器的代码出bug,抛出了异常,当前天气显示器就收不到更新了。
解决方法是在通知时捕获每个观察者的异常,隔离错误。修改notifyObservers方法:
void WeatherData::notifyObservers() override {
std::lock_guard<std::mutex> lock(mutex_);
for (auto& weak_observer : observers_) {
if (auto observer = weak_observer.lock()) {
try {
observer->update(temperature_, humidity_, pressure_);
} catch (const std::exception& e) {
// 捕获异常并记录日志,不中断后续通知
std::cerr << "观察者更新失败:" << e.what() << std::endl;
} catch (...) {
// 捕获未知异常,确保程序稳定
std::cerr << "观察者更新发生未知错误" << std::endl;
}
}
}
}
这里用try-catch捕获所有异常,并用日志记录错误信息。这样即使某个观察者出问题,也不会影响其他观察者的正常工作,提升了系统的稳定性。
4.4 现代C++优化:用lambda简化观察者实现
如果观察者的逻辑比较简单,每次都创建一个新的类来实现IObserver接口会比较繁琐。C++11及以后的版本中,可以用std::function和lambda表达式简化实现,让观察者更灵活。
我们来改造一下抽象主题,支持lambda作为观察者:
#include <functional> // 引入function头文件
// 定义更新回调函数类型
using WeatherUpdateCallback = std::function<void(float, float, float)>;
class WeatherData : public ISubject {
private:
// 同时支持传统观察者接口和lambda回调
std::vector<std::weak_ptr<IObserver>> observers_;
std::vector<WeatherUpdateCallback> callbacks_;
std::mutex mutex_;
public:
// 新增:注册lambda回调作为观察者
void registerCallback(WeatherUpdateCallback callback) {
std::lock_guard<std::mutex> lock(mutex_);
if (callback) {
callbacks_.push_back(callback);
}
}
void notifyObservers() override {
std::lock_guard<std::mutex> lock(mutex_);
// 通知传统观察者
for (auto& weak_observer : observers_) {
if (auto observer = weak_observer.lock()) {
// 异常处理逻辑...
observer->update(temperature_, humidity_, pressure_);
}
}
// 通知lambda回调观察者
for (auto& callback : callbacks_) {
try {
callback(temperature_, humidity_, pressure_);
} catch (...) {
// 异常处理...
}
}
}
};
客户端使用时,可以直接传递lambda表达式作为观察者,无需创建新类:
int main() {
WeatherData weather_data;
// 用lambda注册一个简单的观察者,打印温度变化
weather_data.registerCallback([](float temp, float humidity, float pressure) {
std::cout << "\n【Lambda简易观察者】" << std::endl;
std::cout << "温度变化:" << temp << "F" << std::endl;
});
// 数据更新时,lambda会收到通知
weather_data.setWeatherData(72.5f, 65.2f, 101.3f);
return 0;
}
这种方式特别适合简单的观察者场景,能大幅减少代码量,提升开发效率。传统接口和lambda可以结合使用,兼顾灵活性和复杂性。
五、深入理解:观察者模式的优缺点与适用场景
任何设计模式都不是银弹,只有了解它的优缺点,才能在合适的场景中使用。我们结合前面的案例,总结一下观察者模式的核心特性。

5.1 核心优点
-
松耦合:被观察者和观察者通过抽象接口交互,互不依赖具体实现。这是观察者模式最核心的价值,也是解决“紧耦合噩梦”的关键。
-
符合开闭原则:新增观察者只需实现抽象接口并注册,无需修改被观察者代码。就像我们新增一个“语音播报”观察者,完全不用动WeatherData的代码。
-
支持广播通信:被观察者的状态变化可以一次性通知所有观察者,无需逐个调用,适合需要全局同步的场景。
-
职责清晰:被观察者负责维护状态和通知,观察者负责响应,各司其职,提升代码的可维护性。
5.2 潜在缺点
-
通知延迟:如果观察者数量过多,通知所有观察者会消耗较多时间,导致状态更新的延迟。可以通过异步通知(比如用线程池)来优化。
-
内存泄漏风险:如果观察者注销不当(比如忘记从被观察者列表中移除),会导致被观察者持有观察者的引用,引发内存泄漏。智能指针的使用能缓解这个问题。
-
调试复杂:观察者之间可能存在间接依赖,一个状态变化会触发多个观察者的连锁反应,出现问题时调试难度较大。需要做好日志记录。
5.3 典型适用场景
当你的项目中出现以下场景时,就可以考虑使用观察者模式:
-
事件驱动系统:如GUI开发中的按钮点击、菜单选择等事件,按钮是被观察者,点击事件的处理函数是观察者。MFC的消息映射、Qt的信号与槽机制,本质上都是观察者模式的实现。
-
实时数据监控:如股票行情、天气监测、设备状态监控等,数据源是被观察者,各种显示终端和预警系统是观察者。
-
消息订阅-发布系统:如消息队列(MQ)的发布订阅模式,生产者发布消息,消费者订阅消息,中间件充当被观察者的角色。
-
日志系统:日志记录器是被观察者,日志分析器、告警系统、日志存储模块是观察者,当日志产生时,所有模块都会收到通知。
六、实战对比:传统实现 vs 观察者模式

为了让大家更直观地感受到观察者模式的价值,我们对比一下开头提到的“紧耦合”传统实现和观察者模式的差异,用表格清晰展示:
| 对比维度 | 传统紧耦合实现 | 观察者模式实现 | |
|---|---|---|---|
| 耦合度 | 高,被观察者直接依赖具体观察者类 | 低,仅依赖抽象接口 | |
| 扩展性 | 差,新增观察者需修改被观察者代码 | 好,新增观察者只需实现接口并注册 | |
| 维护成本 | 高,修改核心代码易引发连锁错误 | 低,模块独立,职责清晰 | |
| 异常影响 | 单个观察者异常会中断整个流程 | 可通过异常处理隔离错误,不影响全局 | |
| 适用场景 | 简单场景,观察者数量固定 | 复杂场景,观察者数量动态变化 | |
| 代码复用性 | 低,观察者逻辑无法复用 | 高,抽象接口可复用在不同系统 |
从表格可以看出,随着系统复杂度的提升,观察者模式的优势会越来越明显。在小型demo中,传统实现可能更简单,但在企业级项目中,观察者模式带来的可维护性和扩展性是不可或缺的。
七、总结与思考

到这里,关于观察者模式的讲解就基本结束了。我们从实际开发中的耦合问题切入,用天气系统案例手把手实现了基础版本,再针对内存管理、线程安全、异常处理等实际问题进行了进阶优化,最后对比了传统实现和观察者模式的差异。
回顾整个学习过程,我们需要记住的核心不是代码模板,而是观察者模式的设计思想——通过抽象接口解耦对象间的依赖,让系统更灵活、更易于扩展。在C++实现中,要重点关注智能指针的使用(避免内存问题)、互斥锁的同步(保证线程安全)和异常处理(提升系统稳定性),这些都是从“理论”走向“实战”的关键。
最后给大家留一个思考问题:观察者模式和发布-订阅模式有什么区别?很多人会把它们混为一谈,但实际上发布-订阅模式是观察者模式的一种扩展,通过引入“事件总线”(中间件)进一步解耦了被观察者和观察者,使它们不需要直接交互。感兴趣的同学可以深入研究一下,欢迎在评论区交流你的看法。
如果这篇文章对你有帮助,欢迎点赞、收藏、关注,我会持续分享C++开发和设计模式的实战内容。如果有任何问题或建议,也请在评论区留言,我们一起进步!
1289

被折叠的 条评论
为什么被折叠?



