当前位置: 首页 > news >正文

观察者模式

基础

观察者模式(Observer Pattern)是一种设计模式,定义对象间的一对多依赖关系。当一个对象的状态发生改变时,它的所有依赖者(观察者)都会收到通知并自动更新。这个模式常用于事件处理系统,如GUI事件、通知机制等。

观察者模式的关键组成部分

  1. 主题(Subject):被观察的对象,拥有维护和管理观察者的能力,可以在其状态发生变化时通知观察者。
  2. 观察者(Observer):当主题的状态发生变化时,接收通知并进行相应的更新。
  3. 具体主题(Concrete Subject):继承自主题,具体实现主题的功能。
  4. 具体观察者(Concrete Observer):继承自观察者,具体实现更新逻辑。

C++实现观察者模式

1. 定义观察者接口
#include <iostream>
#include <vector>
#include <string>
#include <memory> // 引入智能指针库// 定义抽象观察者类
class Observer {
public:virtual ~Observer() {}virtual void update(const std::string& message_from_subject) = 0;
};

在这里,我们定义了一个纯虚函数 update(),表示所有具体观察者都必须实现的接口。

2. 定义主题接口
class Subject {
public:virtual ~Subject() {}virtual void attach(std::shared_ptr<Observer> observer) = 0;   // 添加观察者virtual void detach(std::shared_ptr<Observer> observer) = 0;   // 移除观察者virtual void notify() = 0;                     // 通知观察者
};

主题接口提供了 attach()detach()notify() 方法,用于添加、移除和通知观察者。

3. 具体主题实现
class ConcreteSubject : public Subject {
private:std::vector<std::shared_ptr<Observer>> observers;  // 维护一个观察者列表std::string message;
public:// 添加观察者void attach(std::shared_ptr<Observer> observer) override {observers.push_back(observer);}// 移除观察者void detach(std::shared_ptr<Observer> observer) override {observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end());}// 通知所有观察者void notify() override {for (auto observer : observers) {observer->update(message);}}// 设置状态,并通知观察者void createMessage(const std::string& message) {this->message = message;notify();}
};

ConcreteSubject 中,我们维护一个观察者的列表。当主题的状态变化时,会通过 notify() 方法通知所有观察者。

4. 具体观察者实现
class ConcreteObserver : public Observer {
private:std::string name;  // 观察者名称std::string message_from_subject;
public:ConcreteObserver(const std::string& name) : name(name) {}void update(const std::string& message_from_subject) override {this->message_from_subject = message_from_subject;display();}void display() const {std::cout << "Observer [" << name << "] received message: " << message_from_subject << std::endl;}
};

具体观察者类实现了 update() 方法,每当接收到来自主题的消息时,它会更新其状态并显示消息。

5. 测试观察者模式
int main() {auto subject = std::make_shared<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->createMessage("Hello Observers!");subject->detach(observer2); // 移除Observer 2subject->createMessage("New Update Available!");return 0;
}

代码解释

  1. 创建主题和观察者ConcreteSubject 实例化后,我们创建了多个观察者对象(ConcreteObserver)。
  2. 注册观察者:通过 attach() 方法将观察者注册到主题上。
  3. 通知观察者:当主题的状态通过 createMessage() 方法改变时,它会调用 notify() 方法通知所有的观察者。
  4. 移除观察者:使用 detach() 方法,可以移除不再需要通知的观察者。
  5. 观察者接收通知:每个观察者在接收到通知后,通过 update() 方法更新自己并输出消息。

运行结果

Observer [Observer 1] received message: Hello Observers!
Observer [Observer 2] received message: Hello Observers!
Observer [Observer 3] received message: Hello Observers!
Observer [Observer 1] received message: New Update Available!
Observer [Observer 3] received message: New Update Available!

小结

  • 观察者模式有助于实现解耦,当一个对象状态发生改变时,会自动通知其依赖的对象,而不需要显式调用它们。
  • 在实际应用中,常用于实现事件通知机制、订阅-发布模式等场景。

改进

代码还可以从以下几个方面进行改进,提升代码的健壮性、灵活性和性能:

1. 使用 std::weak_ptr 避免循环引用

在观察者模式中,如果 SubjectObserver 之间的关系过于复杂,且双方都使用 std::shared_ptr 彼此引用,可能会导致循环引用(也称为“循环依赖”),使得智能指针无法释放内存,导致内存泄漏。

改进措施:使用 std::weak_ptr 打破循环引用。std::weak_ptr 不增加引用计数,它只持有对象的弱引用。

在这个例子中,Subject 持有观察者的 std::weak_ptr,而观察者依旧可以使用 std::shared_ptr

#include <iostream>
#include <vector>
#include <string>
#include <memory>class Observer {
public:virtual ~Observer() {}virtual void update(const std::string& message_from_subject) = 0;
};class Subject {
public:virtual ~Subject() {}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 {
private:std::vector<std::weak_ptr<Observer>> observers;  // 使用weak_ptr打破循环引用std::string message;
public:void attach(std::shared_ptr<Observer> observer) override {observers.push_back(observer);}void detach(std::shared_ptr<Observer> observer) override {observers.erase(std::remove_if(observers.begin(), observers.end(), [&observer](const std::weak_ptr<Observer>& o) {return o.lock() == observer;}), observers.end());}void notify() override {for (auto it = observers.begin(); it != observers.end();) {if (auto observer = it->lock()) {  // lock() 将 weak_ptr 转换为 shared_ptrobserver->update(message);++it;} else {it = observers.erase(it);  // 如果对象已被销毁,移除观察者}}}void createMessage(const std::string& message) {this->message = message;notify();}
};class ConcreteObserver : public Observer {
private:std::string name;std::string message_from_subject;
public:ConcreteObserver(const std::string& name) : name(name) {}void update(const std::string& message_from_subject) override {this->message_from_subject = message_from_subject;display();}void display() const {std::cout << "Observer [" << name << "] received message: " << message_from_subject << std::endl;}
};int main() {auto subject = std::make_shared<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->createMessage("Hello Observers!");subject->detach(observer2);  // 移除Observer 2subject->createMessage("New Update Available!");return 0;
}

改进要点

  • std::weak_ptr 用于 Subject 持有的观察者列表,避免循环引用和内存泄漏。
  • 通过 weak_ptr.lock() 检查对象是否仍然有效,如果观察者已被销毁,将其从列表中移除。

2. 性能优化

  • 延迟通知:在有大量观察者时,通知所有观察者可能会带来性能开销。如果对实时性要求不高,可以引入批处理或者异步通知机制。可以使用 C++ 标准库中的 std::threadstd::async 来实现通知的并行或异步操作。

3. 增强可扩展性

  • 事件过滤:当前的观察者模式是所有观察者对每个通知都作出响应。可以通过在 notify 方法中增加条件逻辑,使观察者只对某些特定的事件类型作出响应,从而减少不必要的通知。例如,可以引入事件枚举,判断是否应该通知某个观察者。
enum class EventType {MESSAGE_UPDATE,DATA_UPDATE
};class Observer {
public:virtual ~Observer() {}virtual void update(EventType type, const std::string& message) = 0;
};class ConcreteSubject : public Subject {// 略...void notify(EventType type) override {for (auto observer : observers) {if (auto o = observer.lock()) {o->update(type, message);}}}// 可以根据不同的事件类型发送不同的通知void createMessage(const std::string& message) {this->message = message;notify(EventType::MESSAGE_UPDATE);}
};

改进要点

  • 添加事件类型判断,通知相关的观察者,避免不必要的更新,提升效率。

4. 线程安全

如果你的项目是多线程环境,考虑将 Subjectattach()detach()notify() 方法添加互斥锁以保证线程安全,避免多线程同时修改观察者列表可能带来的数据竞争。

可以使用 C++11 提供的 std::mutex 实现线程安全:

#include <mutex>class ConcreteSubject : public Subject {
private:std::vector<std::weak_ptr<Observer>> observers;std::string message;std::mutex mtx;  // 互斥锁保证线程安全
public:void attach(std::shared_ptr<Observer> observer) override {std::lock_guard<std::mutex> lock(mtx);  // 加锁observers.push_back(observer);}void detach(std::shared_ptr<Observer> observer) override {std::lock_guard<std::mutex> lock(mtx);  // 加锁observers.erase(std::remove_if(observers.begin(), observers.end(), [&observer](const std::weak_ptr<Observer>& o) {return o.lock() == observer;}), observers.end());}void notify() override {std::lock_guard<std::mutex> lock(mtx);  // 加锁for (auto it = observers.begin(); it != observers.end();) {if (auto observer = it->lock()) {observer->update(message);++it;} else {it = observers.erase(it);}}}// 其他方法略...
};

改进要点

  • 引入互斥锁确保观察者列表的修改和遍历在多线程下的安全性。

5. 日志功能

可以为观察者模式中的 notify() 添加日志功能,记录每次的通知行为和状态变化。借助 std::ofstream 或其他日志库(如 spdlog),可以方便地跟踪整个系统的事件传播过程。

总结:

  • 使用 std::weak_ptr 避免循环引用。
  • 引入 事件类型 过滤以减少不必要的通知。
  • 增加 线程安全机制 以支持多线程环境。
  • 考虑异步或并行的 性能优化
  • 添加日志以增强 调试能力

通过这些改进,代码将变得更加健壮、可扩展且高效。

拓展

观察者模式有许多变种和扩展,常见的变种主要针对不同应用场景下的需求,增强了模式的灵活性、性能和可维护性。下面详细讲解几种常见的观察者模式变种,以及每种变种适用的场景和优势。

1. 推模型 vs 拉模型

这是最经典的观察者模式的变体,主要区别在于通知更新时数据的传递方式。

推模型(Push Model)

在推模型中,主题(Subject)主动将全部数据或状态变化直接推送给所有观察者,不需要观察者自己去请求数据。也就是说,主题在通知观察者时,将所有相关数据作为参数传递给观察者的 update() 方法。

优点

  • 简化了观察者的设计,观察者只需要处理接收到的数据,无需关心如何获取。

缺点

  • 如果数据量大而观察者并不需要全部数据,可能造成不必要的性能开销。

实现示例

void notify() override {for (auto observer : observers) {if (auto o = observer.lock()) {o->update(message);  // 推送所有的message内容}}
}
拉模型(Pull Model)

在拉模型中,观察者主动从主题中拉取数据。当主题状态变化时,它只通知观察者,具体数据由观察者调用主题的接口来获取。

优点

  • 观察者可以只获取自己关心的部分数据,减少数据传输的冗余。
  • 提高了灵活性,主题只负责通知,数据由观察者自己决定是否需要获取。

缺点

  • 观察者和主题耦合度略高,观察者需要知道主题的状态获取接口。

实现示例

void notify() override {for (auto observer : observers) {if (auto o = observer.lock()) {o->update();  // 只通知有变化,不传递具体数据}}
}class ConcreteObserver : public Observer {
private:std::shared_ptr<ConcreteSubject> subject;  // 观察者保存对主题的引用
public:void update() override {std::string data = subject->getMessage();  // 从主题拉取数据std::cout << "Received: " << data << std::endl;}
};

2. 同步观察者模式 vs 异步观察者模式

同步观察者模式

这是经典的观察者模式实现,所有观察者的通知是同步进行的,也就是说,当主题的 notify() 方法调用时,主题会逐一调用每个观察者的 update() 方法。所有观察者必须在 notify() 调用结束前完成更新。

优点

  • 实现简单,适合对实时性要求较高的场景。
  • 主题在所有观察者都完成更新之前,不会继续其他任务。

缺点

  • 如果某个观察者执行时间较长,可能会拖慢整个系统的响应速度。
异步观察者模式

在异步观察者模式中,主题和观察者之间的通知是异步的,通常通过多线程或事件驱动机制来实现。主题只负责发布事件或通知,而观察者在后台线程或通过消息队列处理更新。

优点

  • 提高了系统的响应性,主题不必等待所有观察者完成更新。
  • 适用于观察者更新任务较重或者延迟不敏感的场景。

缺点

  • 由于异步执行,可能会产生竞态条件,需要额外的同步机制保证数据一致性。
  • 调试复杂,观察者收到通知的顺序可能与实际发生顺序不同。

异步实现示例
可以使用 C++11 的 std::asyncstd::thread 实现异步通知。

#include <future>void notify() override {for (auto observer : observers) {if (auto o = observer.lock()) {std::async(std::launch::async, [o]() {o->update();  // 异步调用update});}}
}

3. 事件总线(Event Bus)/ 发布-订阅模式(Publish-Subscribe Pattern)

发布-订阅模式是观察者模式的一种更灵活的扩展。在发布-订阅模式中,发布者和订阅者之间没有直接的引用关系,它们通过中间消息通道事件总线通信。发布者发布事件,订阅者订阅某类事件,事件总线负责路由消息。

这种模式比观察者模式更加解耦,订阅者可以动态订阅某类事件,发布者也无需知道订阅者的存在。

优点:
  • 高度解耦:发布者和订阅者之间没有直接依赖,任何订阅者都可以订阅某个事件。
  • 灵活性强:支持多种事件类型,订阅者可以动态订阅和取消订阅。
缺点:
  • 性能开销大:在大规模系统中,事件路由和消息传递可能会带来性能问题。
  • 调试复杂:由于异步和分布式的特性,调试事件流的路径和问题变得更加困难。

事件总线示例

#include <unordered_map>
#include <vector>
#include <functional>class EventBus {
private:std::unordered_map<int, std::vector<std::function<void()>>> subscribers;  // 事件ID和处理函数映射
public:void subscribe(int eventId, std::function<void()> handler) {subscribers[eventId].push_back(handler);}void publish(int eventId) {if (subscribers.find(eventId) != subscribers.end()) {for (auto& handler : subscribers[eventId]) {handler();  // 通知所有订阅者}}}
};

发布-订阅场景

  • 发布者调用 EventBus.publish(eventId) 发布事件。
  • 订阅者使用 EventBus.subscribe(eventId, handler) 动态订阅感兴趣的事件。

4. 双向观察者模式(Bidirectional Observer Pattern)

在某些场景中,观察者与被观察者之间的依赖关系是双向的。例如,两个对象彼此依赖,A 是 B 的观察者,B 也是 A 的观察者。在这种情况下,可能会引发无限循环更新(因为双方会不断通知对方)。

为了解决这种问题,双向观察者模式通过标记机制避免循环通知。例如,当一个对象已经被更新时,它会标记自己,避免重复通知对方。

实现思路

  • 增加一个标志位或状态变量,表示该对象是否已经被更新。
  • 在通知时先检查该标志位,防止重复更新。
class BidirectionalObserver : public Observer {
private:bool isUpdated = false;  // 标志位
public:void update() override {if (!isUpdated) {isUpdated = true;// 执行更新操作}}void reset() {isUpdated = false;  // 重置状态}
};

5. 层次观察者模式(Hierarchical Observer Pattern)

在某些复杂系统中,观察者可能有层次结构。例如,一个组件的变化会触发其下级组件的变化。层次观察者模式允许在父子关系中传播事件,即一个事件从父级传播到所有子级,类似于 GUI 系统中的事件冒泡机制。

实现思路

  • 主题(Subject)可以有子主题,每个主题通知它的观察者时,也会通知其子主题。
  • 子主题再继续通知它的观察者,直到层次结构的底端。

示例

class HierarchicalSubject : public Subject {
private:std::vector<std::shared_ptr<HierarchicalSubject>> children;
public:void addChild(std::shared_ptr<HierarchicalSubject> child) {children.push_back(child);}void notify() override {Subject::notify();  // 先通知自身的观察者for (auto& child : children) {child->notify();  // 递归通知子主题}}
};

总结:

  • 推模型 vs 拉模型:通过不同的数据传递方式优化性能和灵活性。
  • 同步 vs 异步:同步提供简单实时性,而异步提高系统响应能力,适合并发环境。
  • 发布-订阅模式:进一步解耦观察者与主题,通过事件总线实现灵活通信。
  • 双向观察者模式:解决循环通知问题,适用于双向依赖的场景。
  • 层次观察者模式:适合父子层级结构的事件传递机制,类似 GUI 事件冒泡。

这些变种在不同的应用场景中,可以让观察者模式更加灵活、强大,同时也更适应复杂系统的需求。


http://www.mrgr.cn/news/55378.html

相关文章:

  • Redis 常用指令详解
  • 利用python进行数据处理,得到对自己有利的内容
  • 通过OpenCV实现 Lucas-Kanade 算法
  • 12 django管理系统 - 注册与登录 - 登录
  • 【微信小程序_19_自定义组件(1)】
  • 多IP连接
  • Vue--数据代理
  • 相同的树算法
  • 在做题中学习(63):替换问号
  • vue3学习记录-TransitionGroup
  • 携手并进,智驭教育!和鲸科技与智谱 AI 签署“101 数智领航计划”战略合作协议
  • GB28181协议视频监控平台-鉴权的含义
  • Java 当中使用 “google.zxing ”开源项目 和 “github 的 qrcode-plugin” 开源项目 生成二维码
  • 【04】双样本等方差(t-检验)
  • P3137 [USACO16FEB] Circular Barn S
  • 全面了解 NGINX 的负载均衡算法
  • c语言基础程序——经典100道实例(二)
  • 中电金信重磅发布《金融数据安全治理白皮书》
  • 百度地图引入个性化样式,加载时出现大片白块的解决办法
  • 数据中心母线槽测温监控装置的优势和如何选型
  • Java 创建图形用户界面(GUI)组件详解之下拉式菜单(JMenu、JMenuItem)、弹出式菜单(JPopupMenu)等
  • 协议 MQTT
  • 国产操作系统的介绍与试用
  • 【ios】使用TestFlight将app分发给测试人员(超详细)
  • 微信小程序实现canvas电子签名
  • intel和AMD突然联姻,这操作给我看傻了