观察者模式

基础

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

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

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

C++实现观察者模式

1. 定义观察者接口
cpp 复制代码
#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. 定义主题接口
cpp 复制代码
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. 具体主题实现
cpp 复制代码
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. 具体观察者实现
cpp 复制代码
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. 测试观察者模式
cpp 复制代码
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 2
	subject->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

cpp 复制代码
#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_ptr
                observer->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 2

    subject->createMessage("New Update Available!");

    return 0;
}

改进要点

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

2. 性能优化

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

3. 增强可扩展性

  • 事件过滤 :当前的观察者模式是所有观察者对每个通知都作出响应。可以通过在 notify 方法中增加条件逻辑,使观察者只对某些特定的事件类型作出响应,从而减少不必要的通知。例如,可以引入事件枚举,判断是否应该通知某个观察者。
cpp 复制代码
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 实现线程安全:

cpp 复制代码
#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() 方法。

优点

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

缺点

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

实现示例

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

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

优点

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

缺点

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

实现示例

cpp 复制代码
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 实现异步通知。

cpp 复制代码
#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)

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

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

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

事件总线示例

cpp 复制代码
#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 的观察者。在这种情况下,可能会引发无限循环更新(因为双方会不断通知对方)。

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

实现思路

  • 增加一个标志位或状态变量,表示该对象是否已经被更新。
  • 在通知时先检查该标志位,防止重复更新。
cpp 复制代码
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)可以有子主题,每个主题通知它的观察者时,也会通知其子主题。
  • 子主题再继续通知它的观察者,直到层次结构的底端。

示例

cpp 复制代码
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 事件冒泡。

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

相关推荐
程序猿阿伟2 分钟前
《C++中的魔法:实现类似 Python 的装饰器模式》
java·c++·装饰器模式
Slow菜鸟5 分钟前
Spring 设计模式之装饰器模式
spring·设计模式·装饰器模式
zzzhpzhpzzz5 分钟前
设计模式——装饰器模式
设计模式·装饰器模式
Ethan Wilson10 分钟前
C++/QT可用的websocket库
开发语言·c++·websocket
ergevv1 小时前
类的变量的初始化:成员初始化列表、就地初始化
c++·初始化·
极客代码1 小时前
C/C++ 随机数生成方法
c语言·开发语言·c++·算法
梦起丶2 小时前
CMake 生成器表达式---条件表达式和逻辑运算符
c++·cmake
A_aspectJ2 小时前
Spring 框架中都用到了哪些设计模式?
spring·设计模式·1024程序员节
bin91532 小时前
【热门主题】000013 C++游戏开发全攻略
c++·c
路在脚下@2 小时前
JAVA的设计模式都有那些
java·单例模式·设计模式