观察者模式

观察者模式(Observer Pattern)是软件设计模式中行为型模式的典型代表,它在解耦对象间依赖、实现事件通知机制方面非常有用。我们来深入理解它,帮助你真正掌握。


一、核心思想:一对多的依赖关系

想象一下你订阅了某个公众号。当公众号发布新文章时,所有订阅者都会收到推送。公众号是被观察的对象,订阅者是观察者

这就是观察者模式的核心:

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

  • 一个被观察者 (Subject/Observable):也叫“主题”或“发布者”,维护一个观察者列表。
  • 多个观察者 (Observer):也叫“订阅者”,当被观察者状态改变时,会收到通知。

二、角色与结构 (UML 简化)

  1. Subject (抽象被观察者)

    • 定义管理观察者的方法:attach(Observer*)detach(Observer*)
    • 定义通知观察者的方法:notify()
    • 通常持有一个 std::vector<Observer*> 来存储观察者。
  2. ConcreteSubject (具体被观察者)

    • 继承 Subject
    • 拥有具体的状态(state)。
    • 当状态改变时,调用 notify() 方法通知所有观察者。
  3. Observer (抽象观察者)

    • 定义一个更新接口 update(),所有具体观察者必须实现。
  4. ConcreteObserver (具体观察者)

    • 继承 Observer
    • 实现 update() 方法,定义收到通知后的具体行为。
    • 通常会持有对 ConcreteSubject 的引用,以便获取最新状态。

三、C++ 代码实现 (经典版)

#include <iostream>
#include <vector>
#include <algorithm>

// 1. 抽象观察者
class Observer {
public:
    virtual ~Observer() = default;
    virtual void update() = 0; // 纯虚函数,由子类实现
};

// 2. 抽象被观察者
class Subject {
public:
    virtual ~Subject() = default;

    // 添加观察者
    void attach(Observer* observer) {
        observers.push_back(observer);
    }

    // 移除观察者
    void detach(Observer* observer) {
        observers.erase(
            std::remove(observers.begin(), observers.end(), observer),
            observers.end()
        );
    }

    // 通知所有观察者
    void notify() {
        for (auto* obs : observers) {
            obs->update(); // 调用每个观察者的 update
        }
    }

private:
    std::vector<Observer*> observers; // 观察者列表
};

// 3. 具体被观察者 (例如:天气数据发布器)
class WeatherData : public Subject {
public:
    void setMeasurements(float temperature, float humidity) {
        this->temperature = temperature;
        this->humidity = humidity;
        measurementsChanged(); // 状态改变,触发通知
    }

    // 获取状态的方法,供观察者调用
    float getTemperature() const { return temperature; }
    float getHumidity() const { return humidity; }

private:
    float temperature;
    float humidity;

    // 状态改变时调用
    void measurementsChanged() {
        notify(); // 通知所有观察者
    }
};

// 4. 具体观察者A:当前状况显示
class CurrentConditionsDisplay : public Observer {
public:
    // 必须持有对被观察者的引用,以便获取数据
    explicit CurrentConditionsDisplay(WeatherData* weatherData) 
        : weatherData(weatherData) {
        weatherData->attach(this); // 注册自己为观察者
    }

    ~CurrentConditionsDisplay() override {
        weatherData->detach(this); // 析构时反注册
    }

    void update() override {
        float temp = weatherData->getTemperature();
        float hum = weatherData->getHumidity();
        std::cout << "【当前状况】温度: " << temp 
                  << "°C, 湿度: " << hum << "%\n";
    }

private:
    WeatherData* weatherData; // 依赖具体被观察者 (稍弱)
};

// 5. 具体观察者B:统计显示
class StatisticsDisplay : public Observer {
public:
    explicit StatisticsDisplay(WeatherData* weatherData) 
        : weatherData(weatherData), maxTemp(-100), minTemp(100), tempSum(0), numReadings(0) {
        weatherData->attach(this);
    }

    ~StatisticsDisplay() override {
        weatherData->detach(this);
    }

    void update() override {
        float temp = weatherData->getTemperature();
        tempSum += temp;
        numReadings++;

        maxTemp = std::max(maxTemp, temp);
        minTemp = std::min(minTemp, temp);

        std::cout << "【统计】平均: " << (tempSum / numReadings) 
                  << "°C, 最高: " << maxTemp << "°C, 最低: " << minTemp << "°C\n";
    }

private:
    WeatherData* weatherData;
    float maxTemp, minTemp, tempSum;
    int numReadings;
};

// --- 使用示例 ---
int main() {
    WeatherData weatherData;

    // 创建观察者,它们会自动注册
    CurrentConditionsDisplay currentDisplay(&weatherData);
    StatisticsDisplay statsDisplay(&weatherData);

    // 模拟数据更新
    std::cout << "第一次更新:\n";
    weatherData.setMeasurements(25.0f, 65.0f);

    std::cout << "\n第二次更新:\n";
    weatherData.setMeasurements(28.0f, 70.0f);

    std::cout << "\n第三次更新:\n";
    weatherData.setMeasurements(22.0f, 55.0f);

    return 0;
}

输出:

第一次更新:
【当前状况】温度: 25°C, 湿度: 65%
【统计】平均: 25°C, 最高: 25°C, 最低: 25°C

第二次更新:
【当前状况】温度: 28°C, 湿度: 70%
【统计】平均: 26.5°C, 最高: 28°C, 最低: 25°C

第三次更新:
【当前状况】温度: 22°C, 湿度: 55%
【统计】平均: 25°C, 最高: 28°C, 最低: 22°C

四、深入理解关键点

  1. 松耦合 (Loose Coupling)

    • Subject 只知道 Observer 接口:Subject 不关心具体是哪个观察者,只要实现了 update() 就行。这使得可以轻松添加新的观察者类型,而无需修改 Subject。
    • 观察者依赖具体 Subject:在经典实现中,ConcreteObserver 通常依赖 ConcreteSubject 来获取数据。这是一个弱点。理想情况下,update() 应该通过参数传递数据。
  2. 推模型 vs 拉模型 (Push vs Pull)

    • 拉模型 (Pull):如上例,update() 无参数,观察者主动从 Subject 拉取所需数据。优点是灵活,缺点是观察者需要知道 Subject 的细节。
    • 推模型 (Push)notify() 调用 update(data),将变化的数据直接推送给观察者。优点是高效、解耦更好(观察者无需知道 Subject 细节),缺点是如果数据过多或观察者不需要全部数据,可能浪费。

    改进建议:使用推模型,让 update() 接收必要的数据参数。

  3. 注册与反注册

    • 观察者必须在生命周期结束前从 Subject 反注册 (detach),否则 Subject 持有的指针会变成悬空指针,导致崩溃。
    • 在 C++ 中,通常在观察者的析构函数中调用 detach
  4. 通知顺序

    • notify() 通常按注册顺序通知。如果观察者之间有依赖关系(如一个观察者的结果影响另一个),需要小心处理。
  5. 性能考虑

    • 如果观察者很多,notify() 可能成为性能瓶颈。
    • 可以考虑异步通知、批处理或只通知状态真正改变时。

五、现代 C++ 改进 (推荐)

为了更安全、更灵活,可以使用智能指针和函数对象:

#include <iostream>
#include <vector>
#include <memory>
#include <functional>

class WeatherData {
public:
    using UpdateCallback = std::function<void(float, float)>;

    void addObserver(const UpdateCallback& callback) {
        callbacks.push_back(callback);
    }

    void removeObserver(const UpdateCallback& callback) {
        // 实际中可能需要更复杂的标识机制
        // 这里简化处理,通常用句柄或ID
    }

    void setMeasurements(float temperature, float humidity) {
        this->temperature = temperature;
        this->humidity = humidity;
        notify();
    }

private:
    void notify() {
        for (const auto& cb : callbacks) {
            cb(temperature, humidity); // 推模型,直接传递数据
        }
    }

    std::vector<UpdateCallback> callbacks;
    float temperature, humidity;
};

int main() {
    WeatherData weatherData;

    // 使用 Lambda 作为观察者 (更灵活)
    auto currentDisplay = [](float temp, float hum) {
        std::cout << "【Lambda 显示】温度: " << temp << "°C, 湿度: " << hum << "%\n";
    };

    weatherData.addObserver(currentDisplay);
    weatherData.setMeasurements(30.0f, 80.0f);

    return 0;
}

优点

  • 类型安全:使用 std::function
  • 自动内存管理:配合 std::shared_ptr 可避免悬空指针。
  • 高度灵活:可注册函数、Lambda、成员函数等。

六、应用场景

  • GUI 事件系统:按钮点击,通知所有监听器。
  • MVC 架构:Model (Subject) 状态改变,通知 View (Observer) 更新。
  • 消息队列/发布-订阅系统
  • 游戏开发:玩家死亡事件,通知 UI、音效、AI 等模块。
  • 数据监控与日志

七、总结

观察者模式的强大之处在于解耦动态扩展。你掌握了它,就能设计出更灵活、更易维护的系统。

学习建议

  1. 先理解经典实现的四个角色。
  2. 动手敲一遍代码,体会注册、通知、更新的流程。
  3. 思考“推”和“拉”的区别,并尝试修改代码实现推模型。
  4. 用现代 C++ 特性重构,体会其优势。
  5. 在自己的项目中寻找可以应用观察者模式的场景。

观察者模式 有哪些常见应用场景

观察者模式(Observer Pattern)因其强大的解耦能力事件驱动特性,在软件开发中应用极为广泛。它非常适合“当一个对象状态改变,需要通知其他多个对象”的场景。

以下是观察者模式的一些常见应用场景,涵盖桌面应用、Web开发、游戏、系统设计等多个领域:


1. 图形用户界面 (GUI) 事件处理

这是最经典的应用。

  • 场景:用户点击按钮、输入文本、选择菜单项。
  • 实现
    • Subject:按钮(Button)、文本框(TextBox)等控件。
    • Observer:事件处理器(如 onClick()onTextChanged())。
  • 例子
    • 一个“提交”按钮被点击时,需要通知“表单验证器”进行验证,并通知“数据保存器”保存数据。
    • 滑动条(Slider)值改变时,通知多个显示组件更新数值。

2. 模型-视图-控制器 (MVC) 架构

这是观察者模式的教科书级应用。

  • 场景:数据(Model)改变时,自动更新用户界面(View)。
  • 实现
    • SubjectModel(数据模型)。
    • ObserverView(视图组件)。
  • 例子
    • 在股票行情软件中,股票价格(Model)实时变动,多个图表和列表(View)自动刷新显示最新价格。
    • 文档编辑器中,文档内容改变,状态栏、预览窗格等视图同步更新。

3. 发布-订阅系统 (Pub-Sub)

观察者模式是发布-订阅模式的基础。

  • 场景:消息中间件、事件总线。
  • 实现
    • Subject消息主题(Topic)或事件总线(Event Bus)
    • Observer订阅者(Subscriber)
  • 例子
    • 微服务架构中,订单服务发布“订单创建”事件,库存服务、物流服务、通知服务等订阅该事件并执行相应逻辑。
    • 游戏中的事件系统:EventManager 发布“玩家死亡”事件,音效系统、UI系统、AI系统等订阅并响应。

4. 游戏开发

游戏逻辑复杂,事件繁多,观察者模式非常适用。

  • 场景:游戏状态变化、角色行为、UI更新。
  • 例子
    • 玩家生命值归零 → 通知“死亡动画播放器”、“得分系统”、“游戏结束UI”。
    • 收集到金币 → 通知“音效系统”播放音效、“UI系统”更新金币计数、“成就系统”检查成就。
    • 游戏暂停 → 通知所有活动的游戏对象(敌人、主角、特效)暂停。

5. 数据绑定与响应式编程

现代前端框架的核心思想。

  • 场景:数据模型与UI的自动同步。
  • 实现
    • Subject:响应式数据变量(如 Vue 的 data,React 的 state)。
    • Observer:依赖该数据的 UI 组件或计算属性。
  • 例子
    • 在 Vue.js 中,data 中的 message 改变时,所有使用 {{ message }} 的模板自动更新。
    • RxJS 中的 Observable 和 Observer,实现数据流的监听与响应。

6. 日志与监控系统

  • 场景:系统发生关键事件时,需要记录日志、发送告警、更新监控面板。
  • 例子
    • 系统出现错误 → 通知“日志记录器”写入日志、“邮件告警器”发送邮件、“监控仪表盘”显示错误数。
    • 服务器CPU使用率超过阈值 → 通知“告警系统”、“自动扩容模块”。

7. 配置管理

  • 场景:应用配置动态更新,需要通知所有依赖该配置的模块。
  • 例子
    • 用户修改了“主题颜色”配置 → 通知所有UI组件重新加载主题。
    • 后台管理界面修改了“最大连接数” → 通知数据库连接池调整大小。

8. 财务与交易系统

  • 场景:价格变动、交易达成、订单状态更新。
  • 例子
    • 股票价格变动 → 通知所有持有该股票的用户的交易界面、风险控制系统、算法交易模块。
    • 订单状态从“待支付”变为“已支付” → 通知库存系统扣减库存、物流系统准备发货、用户通知系统发送短信。

9. 多人协作应用

  • 场景:一个用户的操作需要实时同步给其他用户。
  • 例子
    • 在线文档编辑(如 Google Docs):用户A输入文字 → 通过服务器广播 → 用户B、C的文档视图自动更新。
    • 协同白板:用户A画了一条线 → 其他用户的白板上同步出现这条线。

10. 缓存失效

  • 场景:数据源更新时,需要使相关缓存失效。
  • 例子
    • 数据库中用户信息更新 → 通知“缓存服务”删除该用户的缓存数据。

总结:何时使用观察者模式?

当你遇到以下情况时,可以考虑使用观察者模式:

  1. 一个对象的改变需要同时改变其他多个对象
  2. 对象之间耦合度要尽可能低(发布者不关心谁订阅,订阅者不关心谁发布)。
  3. 需要支持动态添加或移除“监听者”
  4. 事件驱动的系统,需要对特定事件做出反应。

关键优势

  • 松耦合:Subject 和 Observer 之间是抽象耦合。
  • 可扩展性:可以轻松添加新的 Observer,无需修改 Subject。
  • 支持广播通信:一个 Subject 可以通知多个 Observer。

掌握这些应用场景,你就能在实际项目中灵活运用观察者模式,设计出更优雅、更健壮的软件系统。

有哪些经典案例使用了观察者模式

观察者模式是软件设计中最基础、最广泛使用的模式之一。许多经典框架、库和系统都深度应用了观察者模式,它往往是这些系统能够实现高内聚、低耦合和事件驱动架构的关键。

以下是几个极具代表性的经典案例,它们不仅在技术上应用了观察者模式,更在软件工程史上具有里程碑意义:


1. Java AWT/Swing 事件模型 (GUI 事件系统)

  • 地位:这是观察者模式最著名、最权威的官方应用案例之一。
  • 实现方式
    • java.util.Observable 类(被观察者)
    • java.util.Observer 接口(观察者)
  • 经典场景:在 Java Swing 桌面应用中,用户点击一个按钮(JButton)。
    • Subject (被观察者)JButton 对象。
    • Observer (观察者):实现了 ActionListener 接口的类(如 MyActionListener)。
    • 注册button.addActionListener(myListener);
    • 通知:当按钮被点击时,button 会调用 myListener.actionPerformed(event)
  • 意义:它确立了“组件-监听器”这一 GUI 编程范式,后续几乎所有 GUI 框架(包括 C# WinForms、Android、Qt 等)都借鉴了这一思想。

2. .NET Framework 事件与委托 (Delegates and Events)

  • 地位:C# 语言级支持的观察者模式,极其优雅和高效。
  • 实现方式
    • 使用 delegate(委托)定义回调方法的签名。
    • 使用 event 关键字声明事件,它是对委托的封装,提供 += (订阅) 和 -= (取消订阅) 操作。
  • 经典场景:Windows Forms 或 WPF 应用中的按钮点击。
    // 定义事件
    public event EventHandler<MyEventArgs> DataChanged;
    
    // 触发事件(通知观察者)
    protected virtual void OnDataChanged(MyEventArgs e) {
        DataChanged?.Invoke(this, e);
    }
    
    // 使用
    myObject.DataChanged += MyHandler; // 订阅
  • 意义:.NET 将观察者模式内化为语言特性,使得事件处理简洁、类型安全,是现代事件驱动编程的典范。

3. Spring Framework 事件发布 (ApplicationEvent & ApplicationListener)

  • 地位:企业级 Java 开发中解耦业务逻辑的经典实践。
  • 实现方式
    • ApplicationEventPublisher (被观察者/发布者)
    • ApplicationListener<T> (观察者/监听者)
  • 经典场景:用户注册后,需要发送欢迎邮件、初始化用户数据、记录日志。
    // 1. 发布事件
    applicationEventPublisher.publishEvent(new UserRegisteredEvent(userId));
    
    // 2. 监听事件
    @EventListener
    public void handleUserRegistration(UserRegisteredEvent event) {
        sendWelcomeEmail(event.getUserId());
    }
  • 意义:避免了在“用户注册”主流程中硬编码所有后续操作,实现了业务逻辑的解耦可扩展性。新增一个监听器(如发短信)无需修改注册代码。

4. RxJava / ReactiveX (响应式编程库)

  • 地位:将观察者模式提升到“数据流”层面的集大成者。
  • 实现方式
    • Observable (被观察者):代表一个可以发射数据流的源头。
    • Observer (观察者):接收并处理数据流。
    • 核心是 subscribe(Observer) 方法。
  • 经典场景:处理异步数据流,如网络请求、用户输入、传感器数据。
    Observable.fromCallable(() -> fetchDataFromNetwork())
              .subscribeOn(Schedulers.io())
              .observeOn(AndroidSchedulers.mainThread())
              .subscribe(new Observer<String>() {
                  @Override
                  public void onNext(String data) {
                      // 处理数据 (观察者收到通知)
                  }
              });
  • 意义:它不仅仅是简单的“状态改变通知”,而是将一切视为流(Everything is a stream),通过组合、转换、过滤等操作符构建复杂的数据处理管道,是现代异步编程的基石。

5. Vue.js / React 等前端框架 (响应式数据绑定)

  • 地位:现代前端框架的核心机制。
  • 实现方式
    • Vue.js:使用 Object.defineProperty 或 Proxy 拦截数据的 get 和 set。在 get 时收集依赖(订阅),在 set 时触发更新(通知)。
    • React:虽然机制不同(基于状态更新触发 render),但其 useState 和 useEffect 的思想与观察者模式高度相似。useEffect 可以看作是“观察”某些状态的变化。
  • 经典场景:模板中 {{ message }} 显示的数据。
    • 当 JavaScript 中的 message 变量改变时,DOM 自动更新。
  • 意义:开发者无需手动操作 DOM,只需关注数据,框架自动处理视图更新,极大提升了开发效率和代码可维护性。

6. Qt 框架的信号与槽 (Signals and Slots)

  • 地位:C++ GUI 开发中观察者模式的优雅实现。
  • 实现方式
    • signals:被观察者发出的信号。
    • slots:观察者提供的槽函数,用于响应信号。
    • 使用 connect() 函数建立连接。
  • 经典场景:按钮点击触发动作。
    connect(button, &QPushButton::clicked, this, &MyClass::handleClick);
  • 意义:提供了类型安全、松耦合的对象间通信机制,是 Qt 框架的核心特色之一。

总结

这些经典案例的共同点是:

  1. 解耦:发布者(Subject)不关心谁订阅,订阅者(Observer)不关心谁发布。
  2. 可扩展:可以动态添加新的观察者,无需修改发布者代码。
  3. 事件驱动:系统围绕“事件”构建,逻辑清晰。

学习这些经典案例,不仅能理解观察者模式的应用,更能体会到优秀软件设计的思想。你可以从 Java Swing 或 .NET 的事件模型开始,它们是理解该模式最直观的入口。

观察者模式在实际项目中如何优化?

在实际项目中,直接使用“教科书式”的观察者模式可能会遇到性能、内存安全、可维护性等问题。因此,对其进行优化至关重要。

以下是针对不同痛点的实用优化策略,帮助你在真实项目中更高效、更安全地应用观察者模式:


1. 内存安全与生命周期管理 (C++ 尤其重要)

问题:经典实现中,Subject 持有 Observer* 原始指针。如果 Observer 对象被销毁而未从 Subject 反注册,Subject 的指针会变成悬空指针(Dangling Pointer),再次调用 notify() 会导致程序崩溃。

优化方案
  • 使用智能指针 + 弱引用 (Weak Reference)

    #include <memory>
    #include <vector>
    
    class Observer {
    public:
        virtual ~Observer() = default;
        virtual void update() = 0;
    };
    
    class Subject {
    public:
        // 使用 weak_ptr 避免循环引用和悬空指针
        void attach(std::weak_ptr<Observer> observer) {
            observers.push_back(observer);
        }
    
        void notify() {
            // 过滤掉已销毁的观察者
            observers.erase(
                std::remove_if(observers.begin(), observers.end(),
                    [](const std::weak_ptr<Observer>& wp) { return wp.expired(); }),
                observers.end()
            );
    
            for (auto& wp : observers) {
                if (auto sp = wp.lock()) { // 获取 shared_ptr
                    sp->update();
                }
            }
        }
    
    private:
        std::vector<std::weak_ptr<Observer>> observers;
    };
    • 优点:自动处理对象销毁,无需手动 detach
    • 缺点:每次通知都需要遍历并检查 expired(),稍有性能开销。
  • 提供自动反注册机制Observer 的析构函数中自动调用 detach()

    class ConcreteObserver : public Observer {
    public:
        ConcreteObserver(Subject* s) : subject(s) { subject->attach(this); }
        ~ConcreteObserver() override { subject->detach(this); } // 自动反注册
        void update() override { /* ... */ }
    private:
        Subject* subject;
    };
    • 注意:确保 Subject 的生命周期长于 Observer,否则仍可能出问题。

2. 性能优化

问题:当观察者数量庞大或 notify() 调用频繁时,遍历所有观察者并调用 update() 可能成为性能瓶颈。

优化方案
  • 避免无意义的通知 (Change Detection) 只有在状态真正改变时才通知。

    void WeatherData::setMeasurements(float temp, float hum) {
        if (this->temperature == temp && this->humidity == hum) {
            return; // 状态未变,不通知
        }
        this->temperature = temp;
        this->humidity = hum;
        notify();
    }
  • 异步通知 (Async Notification) 将通知放入工作队列,由后台线程处理,避免阻塞主线程(尤其在 GUI 或游戏主循环中)。

    void Subject::notifyAsync() {
        std::thread([observers = this->observers]() {
            for (auto* obs : observers) {
                obs->update(); // 在后台线程执行
            }
        }).detach();
    }
    • 注意:需考虑线程安全和数据同步。
  • 批量更新 (Batch Updates) 在一帧或一个事件周期内,只通知一次,而不是每次状态微小变化都通知。

    • 例子:游戏引擎中,将所有“实体位置变化”事件收集起来,每帧末尾统一通知渲染系统。

3. 解耦优化:推模型 vs 拉模型

问题:经典“拉模型”要求 Observer 知道 ConcreteSubject 的细节来获取数据,耦合度高。

优化方案
  • 采用“推模型” (Push Model) 将变化的数据作为参数传递给 update()
    class Observer {
    public:
        virtual void update(float temperature, float humidity) = 0;
        // 或传递一个数据包
        // virtual void update(const SensorData& data) = 0;
    };
    
    void WeatherData::notify() {
        for (auto* obs : observers) {
            obs->update(temperature, humidity); // 推送数据
        }
    }
    • 优点Observer 无需知道 WeatherData,解耦更好;减少对 Subject 的依赖调用。

4. 灵活性优化:使用函数对象 (Function Objects)

问题:继承 Observer 接口不够灵活,尤其是对于简单的回调。

优化方案
  • 使用 std::function 和 Lambda
    #include <functional>
    #include <vector>
    
    class Subject {
    public:
        using Callback = std::function<void(float, float)>;
    
        void addObserver(Callback cb) {
            callbacks.push_back(cb);
        }
    
        void notify(float temp, float hum) {
            for (const auto& cb : callbacks) {
                cb(temp, hum); // 调用函数对象
            }
        }
    
    private:
        std::vector<Callback> callbacks;
    };
    
    // 使用示例
    Subject subject;
    subject.addObserver([](float t, float h) {
        std::cout << "温度: " << t << ", 湿度: " << h << "\n";
    });
    • 优点:支持函数、Lambda、成员函数绑定(std::bind),极其灵活。
    • 结合智能指针:可以返回一个“句柄”(Handle),用于取消订阅。

5. 功能增强:过滤与选择性通知

问题:并非所有观察者都关心所有类型的通知。

优化方案
  • 基于事件类型的通知
    enum class EventType { TEMPERATURE_CHANGED, HUMIDITY_CHANGED, PRESSURE_CHANGED };
    
    class Subject {
    public:
        void notify(EventType type, float value) {
            // 只通知对该事件类型感兴趣的观察者
            for (auto& [callback, eventTypes] : listeners) {
                if (eventTypes.count(type)) {
                    callback(type, value);
                }
            }
        }
    
        void addListener(Callback cb, const std::set<EventType>& events) {
            listeners.emplace_back(cb, events);
        }
    };

6. 调试与监控

  • 添加日志:在 attachdetachnotify 时打印日志,便于调试。
  • 监控观察者数量:提供接口查询当前观察者数量,防止内存泄漏。

总结:实际项目中的最佳实践

优化目标 推荐方案
内存安全 使用 std::weak_ptr 或确保自动反注册
高性能 变化检测、异步通知、批量更新
高解耦 采用“推模型”,避免观察者主动拉取数据
高灵活性 使用 std::function + Lambda
易用性 提供清晰的 API,支持按事件类型过滤

核心思想:不要拘泥于“四角色”的经典结构。在现代 C++ 中,以“事件总线”或“信号-槽” 的思想来理解和实现观察者模式,往往更符合实际需求。

通过这些优化,你的观察者模式实现将更加健壮、高效,能够胜任复杂项目的要求。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐