C++实用技巧之 — 观察者模式详解
目录
一、系统学习前的思考
- 观察者模式被认为是一种行为设计模式。
它通常用于实现“发布-订阅”机制,这样多个观察者可以订阅某个主题的对象,并在该主题发生变化时自动得到通知。听起来很实用,尤其是在构建事件驱动的系统时。 - 那我应该从哪里开始呢?
或许我应该先查一下观察者模式的基本概念和结构。根据一些资料,观察者模式包括两个主要角色:观察者(Observer)和主题(Subject)。主题维护一个观察者列表,并在状态变化时通知所有观察者。这似乎与发布-订阅模式类似,但更面向对象。
我觉得理解观察者模式的关键在于如何定义观察者的接口,以及主题如何维护和通知这些观察者。可能需要使用接口或抽象类来定义观察者应该实现的方法。 - 举个例子
假设我有一个气象站(Subject),它会发布温度、湿度等数据。多个天气预报应用(Observers)订阅这个气象站的数据。每当气象站更新数据时,所有订阅的应用都会收到通知,更新自己的显示。
那么,在C++中如何实现呢?我觉得可以定义一个Observer接口,其中包含一个update方法,用于接收主题的通知。然后,主题类会维护一个观察者的列表(Observer对象的集合)。
让我尝试写出一些伪代码:
class Observer {
public:virtual void update(float temp, float humidity, float pressure) = 0;
};class WeatherData : public Subject {
private:float temperature;float humidity;float pressure;std::vector<Observer*> observers;public:void registerObserver(Observer* o) {observers.push_back(o);}void removeObserver(Observer* o) {// 从observers中删除o,通常需要编写一个循环来实现}void notifyObservers() {for (Observer* o : observers) {o->update(temperature, humidity, pressure);}}void measurementsChanged() {notifyObservers();}// 其他方法如setMeasurements
};class CurrentConditionsDisplay : public Observer {
private:float temperature;float humidity;float pressure;public:void update(float temp, float humi, float press) override {temperature = temp;humidity = humi;pressure = press;display();}void display() {// 显示当前天气状况}
};
- 看起来收获很大,但当我实际尝试在C++中实现时,有些地方让我困惑。
例如:
如何正确管理观察者的生命周期,避免内存泄漏?
是否需要使用智能指针代替原始指针?
另外,实现Subject接口时,如何有效地维护和更新观察者列表。 - 在安全性方面
C++的观察者模式是否容易出现竞态条件(racecondition)?
如果有多个线程同时订阅或取消订阅,是否会引发问题?
是否需要使用互斥锁来保护访问? - 此外,我发现有些资料中的Subject使用了抽象类,而有些则使用了接口。这在C++中可能会不同,因为我需要显式地定义接口,可能需要使用纯虚基类。
- 我还需要极大地考虑性能问题。如果一个主题有成千上万的观察者,每次通知都会遍历整个列表,这是否会导致性能瓶颈?是否有一种高效的通知机制?
- 哦,还有迭代器的使用。在通知所有观察者时,如何防止迭代过程中观察者列表发生变化导致迭代器失效?有没有办法安全地遍历观察者列表?
- 这些问题让我意识到,尽管观察者模式的基本概念不难理解,但在实际实现和实时应用中,还有很多细节需要考虑。此外,我需要确保代码的可扩展性,以便未来可以轻松地添加新的观察者类型或修改通知机制。
- 我需要查阅更多的资料,查看如何在实际项目中实现观察者模式,看看专家们是如何处理这些细节的。同时,尝试在实际项目中应用,通过实验来理解可能遇到的问题和最佳实践。
总结:
1. 理解观察者模式的基本结构,包括Observer和Subject。
2. 研究如何在C++中正确实现Observer接口,确保多态性。
3. 学习如何管理观察者的注册和注销,避免内存泄漏。
4. 考虑线程安全和性能优化,尤其是在高并发场景下。
5. 通过实际项目和实验,深入理解模式的实际应用和潜在问题。
二、观察者模式详解
1. 模式的定义
观察者模式(Observer Pattern)属于行为设计模式,旨在定义对象之间的一对多依赖关系,使得当一个对象(主题/被观察者)状态改变时,所有依赖于它的对象(观察者)自动通知并更新。
2. 主要角色
• 主题(Subject/被观察者) :维护观察者列表,提供注册和注销观察者的方法,并在状态变化时通知所有注册的观察者。
• 观察者(Observer) :定义一个更新接口,当接收到主题的通知时,执行相应的更新逻辑。
3. 模式的结构
• EventManager(事件管理器)(可选) :用于管理多个主题和观察者之间的关系,简化注册和注销过程。
4. 实现步骤
• 定义观察者接口 :创建一个基类或抽象类,定义update方法。
• 创建具体观察者类 :继承观察者接口,并实现update方法,定义具体的更新逻辑。
• 实现主题类 :维护一个观察者列表,提供registerObserver、removeObserver和notifyObservers方法。
• 在主题中添加状态变化处理逻辑 :当主题的状态变化时,调用notifyObservers以通知所有观察者。
5. 优点
• 松耦合 :主题和观察者之间保持松耦合,减少相互依赖,提升系统的可维护性和可扩展性。
• 广播通信 :改变一个主题的状态,可以通知所有注册的观察者,实现广播通信。
• 易于扩展 :新增观察者时,无需修改现有代码,遵循开闭原则。
6. 缺点
• 潜在的性能问题 :通知大量观察者时,可能导致性能瓶颈。
• 复杂的依赖关系管理 :观察者的注册和注销需要谨慎处理,避免内存泄漏或访问已删除的观察者。
7. 实际应用
7.1 代码实现
以下示例展示了如何在C++中实现观察者模式:
#include <vector>
#include <memory>
#include <string>class Observer {
public:virtual void update(float temp, float humidity, float pressure) = 0;virtual ~Observer() = default;
};class DisplayElement {
public:virtual void display() = 0;virtual ~DisplayElement() = default;
};class WeatherData {
private:float temperature;float humidity;float pressure;std::vector<std::shared_ptr<Observer>> observers;void notifyObservers() {for (const auto& observer : observers) {observer->update(temperature, humidity, pressure);}}public:// 注册观察者void registerObserver(std::shared_ptr<Observer> observer) {observers.push_back(observer);}// 注销观察者void removeObserver(std::shared_ptr<Observer> observer) {observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end());}void setMeasurements(float t, float h, float p) {temperature = t;humidity = h;pressure = p;measurementsChanged();}void measurementsChanged() {notifyObservers();}
};class CurrentConditionsDisplay : public Observer, public DisplayElement {
private:float temperature;float humidity;float pressure;std::string name;public:CurrentConditionsDisplay(const std::string& name) : name(name) {}~CurrentConditionsDisplay() override = default;void update(float temp, float humi, float press) override {temperature = temp;humidity = humi;pressure = press;display();}void display() override {std::cout << "Current conditions(s" << name << "): "<< temperature << "F degrees and " << humidity << "% humidity\n";}
};int main() {std::shared_ptr<WeatherData> weatherData = std::make_shared<WeatherData>();std::shared_ptr<CurrentConditionsDisplay> currentDisplay1 = std::make_shared<CurrentConditionsDisplay>("Display 001");std::shared_ptr<CurrentConditionsDisplay> currentDisplay2 = std::make_shared<CurrentConditionsDisplay>("Display 002");weatherData->registerObserver(currentDisplay1);weatherData->registerObserver(currentDisplay2);weatherData->setMeasurements(80, 65, 30.4f);weatherData->setMeasurements(82, 70, 29.9f);weatherData->removeObserver(currentDisplay2);weatherData->setMeasurements(78, 90, 29.2f);return 0;
}
7.2 说明
• Observer接口 :定义了纯虚函数update,所有观察者必须实现此方法。
• DisplayElement接口 :定义了display方法,用于显示当前状态。
• WeatherData类 :主题类,维护了 observrs 向量,用来存储所有注册的观察者。提供registerObserver和removeObserver方法用于注册和注销观察者。setMeasurements方法更新传感器数据后,调用measurementsChanged,后者通知所有观察者。
• CurrentConditionsDisplay类 :具体观察者,实现update方法以更新显示数据,并通过display方法呈现当前状况。
7.3 高级主题
• 使用智能指针管理生命周期 :通过std::shared_ptr管理观察者的生命周期,避免内存泄漏。
• 线程安全 :在多线程环境下访问主题或观察者时,应使用互斥锁(如std::mutex)来保护共享资源。
• 事件管理器(可选) :对于复杂的系统,可以使用事件管理器来统一管理主题和观察者的关系,简化注册过程。
7.4 优点总结
• 松耦合设计 :主题和观测者之间无需知道彼此的具体实现,只需通过接口通信,系统更易于维护和扩展。
• 广播式通信 :一个主题的状态变化可以同时通知多个观察者。
• 动态管理观察者 :可以在运行时动态注册或注销观察者,提升系统的灵活性。
7.5 缺点总结
• 性能问题 :通知大量观察者可能带来性能开销,特别是在高频率更新的场景中。
• 潜在的竞态条件 :在多线程环境下,若不采取同步措施,可能会导致不一致状态。
7.6 应用原则
• 合适场景 :当系统中存在一对多的依赖关系,且希望自动通知变化时适用。
• 避免过度使用 :虽然观察者模式很灵活,但滥用可能导致系统复杂性增加。
7.7 相关设计模式
• 发布-订阅模式(Publish-Subscribe) :观察者模式的扩展,允许主题和观察者间更松散的耦合,支持频道或主题过滤。
• 模板方法模式(Template Method) :与观察者模式结合,可以在主题中定义算法的钩子方法(hook),允许观察者自定义部分行为。
• 迭代器模式(Iterator) :用于遍历观察者列表,避免暴露主题的内部细节。
7.8 解决常见问题
• 内存泄漏 :使用智能指针管理观察者的生命周期,避免显式内存管理带来的风险。
• 线程安全 :在主题通知和注册/注销操作时,使用互斥锁或者其他同步机制保护共享资源的安全。
• 动态扩展 :允许在运行时通过反射或工厂模式动态加载新的观察者,提升系统的灵活性。
7.9 总结
观察者模式在C++中是一种强大且灵活的设计模式,适用于处理对象间的动态一对多依赖关系。通过合理设计接口和使用智能指针、线程同步机制,可以有效克服其潜在缺点,构建高效且易于维护的系统。熟悉这些概念和实现细节,将有助于开发人员在实际项目中更好地应用观察者模式,提升代码的质量和系统的可扩展性。