C++ 观察者模式

EventBus事件总线(观察者模式):https://blog.csdn.net/Mason___/article/details/157769142?spm=1001.2014.3001.5501

C++观察者模式的核心目的是解耦对象间的依赖关系。

核心****目标是:定义对象间的一对多依赖关系,当一个对象(被观察者)的状态发生变化时,所有依赖它的对象(观察者)会自动收到通知并更新

一、观察者模式的核心目标与设计思想

1.1 核心目标

  1. 解耦被观察者与观察者:被观察者无需知道观察者的具体类型和实现,仅依赖抽象接口;观察者也无需关注被观察者的内部逻辑,只需响应状态变化通知。
  2. 支持动态订阅/取消订阅:观察者可随时加入或退出订阅关系,被观察者无需修改代码即可适配。
  3. 广播通知:被观察者状态变化时,自动通知所有注册的观察者,确保状态一致性。
  4. 单一职责:被观察者负责维护状态和通知逻辑,观察者负责处理通知(如更新 UI、执行业务逻辑),各司其职。

1.2 核心设计思想

  • 依赖倒置原则:被观察者依赖观察者的抽象接口(而非具体实现),观察者依赖被观察者的抽象接口,避免紧耦合。
  • 开闭原则:新增观察者时,无需修改被观察者的代码;新增被观察者时,观察者可通过抽象接口适配,无需修改自身逻辑。
  • 事件驱动:以"状态变化"为事件,触发观察者的响应,符合事件驱动编程模型。

二、观察者模式的核心结构(4大角色)

观察者模式的结构由 4 个核心角色组成,严格遵循"依赖抽象"原则:

角色 职责描述
抽象被观察者(Subject) 定义被观察者的核心接口:注册观察者、取消注册、通知所有观察者。
具体被观察者(ConcreteSubject) 实现抽象被观察者接口,维护自身状态;状态变化时,调用通知方法触发观察者更新。
抽象观察者(Observer) 定义观察者的核心接口:接收被观察者的通知并执行更新操作(纯虚函数)。
具体观察者(ConcreteObserver) 实现抽象观察者接口,根据被观察者的通知执行具体业务逻辑(如更新 UI、记录日志)。

三、代码示例

cpp 复制代码
#include <iostream>
#include <vector>
#include <memory>
#include <algorithm>
 
// ==================== 观察者接口 ====================
class Observer {
public:
    virtual ~Observer() = default;
    
    // 更新方法 - 主题状态改变时被调用
    virtual void update(int state) = 0;
    
    // 可选:获取观察者标识
    virtual std::string getName() const = 0;
};
 
// ==================== 主题接口 ====================
class Subject {
public:
    virtual ~Subject() = default;
    
    // 注册观察者
    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 {
public:
    void attach(std::shared_ptr<Observer> observer) override {
        observers_.push_back(observer);
        std::cout << "添加观察者: " << observer->getName() << std::endl;
    }
    
    void detach(std::shared_ptr<Observer> observer) override {
        auto it = std::find(observers_.begin(), observers_.end(), observer);
        if (it != observers_.end()) {
            observers_.erase(it);
            std::cout << "移除观察者: " << observer->getName() << std::endl;
        }
    }
    
    void notify() override {
        std::cout << "\n=== 通知所有观察者 ===" << std::endl;
        for (auto& observer : observers_) {
            if (observer) {
                observer->update(state_);
            }
        }
    }
    
    // 业务方法:改变状态
    void setState(int state) {
        std::cout << "\n主题状态改变: " << state_ << " -> " << state << std::endl;
        state_ = state;
        notify();  // 状态改变后自动通知
    }
    
    int getState() const {
        return state_;
    }
 
private:
    std::vector<std::shared_ptr<Observer>> observers_;
    int state_ = 0;
};
 
// ==================== 具体观察者实现 ====================
class ConcreteObserver : public Observer {
public:
    ConcreteObserver(std::string name) : name_(std::move(name)) {}
    
    void update(int state) override {
        std::cout << "观察者 [" << name_ << "] 收到更新,新状态: " << state << std::endl;
    }
    
    std::string getName() const override {
        return name_;
    }
 
private:
    std::string name_;
};
 
// ==================== 使用示例 ====================
int main() {
    // 创建主题
    auto subject = std::make_unique<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->setState(100);
    
    // 移除一个观察者
    subject->detach(observer2);
    
    // 再次改变状态
    subject->setState(200);
    
    return 0;
}

输出:

cpp 复制代码
添加观察者: Observer-1
添加观察者: Observer-2
添加观察者: Observer-3
 
主题状态改变: 0 -> 100
 
=== 通知所有观察者 ===
观察者 [Observer-1] 收到更新,新状态: 100
观察者 [Observer-2] 收到更新,新状态: 100
观察者 [Observer-3] 收到更新,新状态: 100
移除观察者: Observer-2
 
主题状态改变: 100 -> 200
 
=== 通知所有观察者 ===
观察者 [Observer-1] 收到更新,新状态: 200
观察者 [Observer-3] 收到更新,新状态: 200

四、观察者模式的适用场景与反场景

4.1 适用场景

  1. 事件驱动系统:如 GUI 框架(按钮点击、窗口关闭等事件)、游戏引擎(角色移动、碰撞检测事件)。
  2. 状态同步场景:如分布式系统中的数据同步(主节点状态变化,从节点自动同步)、缓存更新(数据源变化,缓存自动刷新)。
  3. 消息通知场景:如日志系统(核心模块状态变化,日志模块自动记录)、报警系统(监控指标异常,报警模块自动通知)。
  4. 解耦需求场景:被观察者和观察者需要解耦,避免紧耦合(如业务模块与通知模块分离)。

4.2 反场景(不建议使用)

  1. 观察者数量极少且固定:如仅 1-2 个观察者,且不会扩展,直接调用函数比观察者模式更简洁。
  2. 同步更新开销过大:如观察者更新逻辑复杂(耗时久),且需同步执行,会导致被观察者阻塞(可改用异步通知解决)。
  3. 循环依赖风险:被观察者和观察者相互引用,可能导致内存泄漏(需用弱指针等方式避免)。
  4. 状态变化过于频繁:如每秒上千次状态变化,观察者频繁更新会导致系统性能下降(需合并通知或节流)。

五、C++ 成熟库中的观察者模式应用

1、 Qt 框架QObject + 信号槽(signals/slots)是观察者模式的经典实现,支持跨线程通知、自动连接/断开,是 Qt 的核心机制。

cpp 复制代码
// Qt 信号槽示例(本质是观察者模式)
class WeatherStation : public QObject {
    Q_OBJECT
signals:
    void temperatureChanged(float temperature);  // 信号(被观察者通知)
public:
    void setTemperature(float temp) {
        emit temperatureChanged(temp);  // 触发信号
    }
};

class Display : public QObject {
    Q_OBJECT
public slots:
    void onTemperatureChanged(float temp) {  // 槽函数(观察者更新)
        qDebug() << "当前温度:" << temp;
    }
};

// 连接信号槽(注册观察者)
WeatherStation station;
Display display;
QObject::connect(&station, &WeatherStation::temperatureChanged, &display, &Display::onTemperatureChanged);

2、Boost 库boost::signals2 提供线程安全的观察者模式实现,支持多播回调、自动管理连接,兼容 C++11 及以上标准。

3、C++ 标准库 :无原生观察者模式实现,但可通过 std::function + std::vector 快速实现轻量级观察者模式(如回调列表)。

六、总结与核心原则

观察者模式的核心是 "解耦发布者与订阅者,支持动态联动",C++ 实现的关键在于:

  1. 依赖抽象接口(Subject/Observer),避免紧耦合。
  2. 解决线程安全、内存管理、状态传递等工程问题。
  3. 结合 C++ 特性(智能指针、互斥锁、变体、函数对象)提升灵活性和安全性。
核心原则
  1. 开闭原则优先:新增观察者/被观察者时,无需修改现有代码。
  2. 最小知识原则:被观察者仅通知必要信息,观察者无需了解被观察者内部逻辑。
  3. 线程安全不可少:多线程场景下必须加锁保护观察者列表。
  4. 内存安全是底线:避免裸指针野指针,优先使用智能指针管理生命周期。

**推模式 vs 拉模式 :**推模式直接传递数据,拉模式传递通知让观察者自己获取

相关推荐
لا معنى له2 小时前
Var-JEPA:联合嵌入预测架构的变分形式 —— 连接预测式与生成式自监督学习 ----论文翻译
人工智能·笔记·学习·语言模型
chase。2 小时前
【学习笔记】让机器人“边想边动”——实时动作分块流策略的执行方法
笔记·学习·机器人
[ ]8983 小时前
Stack_MLAG_知识点梳理
网络·笔记·网络协议
唐樽3 小时前
C++ 竞赛学习路线笔记
c++·笔记·学习
bobasyu3 小时前
Claude Code 源码笔记 -- queryLoop
java·笔记·spring
水云桐程序员4 小时前
Quartus II集成开发环境 |FPGA
笔记·fpga开发·硬件工程·创业创新
禹中一只鱼5 小时前
【Charles 抓包工具笔记】(自用复盘版)
笔记·charles·抓包工具·配置代理
Java面试题总结5 小时前
Nginx 配置笔记
运维·笔记·nginx
一定要AK5 小时前
SpringMVC 入门核心笔记
笔记