C++每日训练 Day 11:观察者模式完整实战篇

📘 本篇从最基础的事件响应原理出发,逐步引导你掌握并实现经典的观察者模式(Observer Pattern)。我们将从概念理解、代码骨架、功能扩展、回调安全,到最终构建一个可复用的事件通知系统,帮助你在 UI 框架、组件通信、插件机制中落地使用这一核心模式。


🔁 Day 10 回顾:生命周期安全的回调机制

核心内容精华总结
shared_ptr自动管理对象释放,防止悬空访问
weak_ptr避免循环引用,回调安全解除绑定
std::function通用封装任意函数对象
SafeEvent 框架构建支持 weak_ptr 的事件注册/触发体系

📌 Day 10 解决了“函数引用对象生命周期失控”问题,为本篇观察者结构打下了技术基础。


🎯 今日目标:掌握观察者模式的本质、结构与实战落地

阶段学习重点
原理认知了解事件模型本质,明确“发布者-订阅者”的作用关系
架构实现搭建注册/注销/通知流程,支持弱引用安全回调
高级特性拓展添加优先级、一键解绑、支持任意参数等可扩展能力
实战代码封装最终形成可复用的 ObserverManager 模块

在这里插入图片描述

✅ 一、从原理开始:什么是观察者模式?

📌 定义(通俗理解):

“一方发出通知,所有订阅它的人都会收到并响应”

🔄 举例:

  • 公众号推送文章,粉丝自动收到通知
  • 电商网站有库存变动,多个下单用户收到提醒
  • 游戏中主角血量变化,UI 自动更新显示

🎯 角色组成:

角色含义
Subject发布者(被观察者):维护观察者列表,负责通知
Observer观察者:注册到 subject,等待触发时调用其方法

✅ 二、最小实现:支持添加/通知

🔸 基础代码骨架:

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

class EventPublisher {
public:
    void addObserver(std::function<void()> cb) {
        observers.push_back(cb);
    }

    void notifyAll() {
        for (auto& cb : observers) {
            cb();
        }
    }

private:
    std::vector<std::function<void()>> observers;
};

int main() {
    EventPublisher ep;

    ep.addObserver([](){ std::cout << "观察者A:收到通知\n"; });
    ep.addObserver([](){ std::cout << "观察者B:收到通知\n"; });

    ep.notifyAll();
}

📌 输出:

观察者A:收到通知
观察者B:收到通知

✅ 三、增强功能:支持解绑 + 生命周期安全

🔸 引入 weak_ptr 管理对象安全

#include <memory>

class ObserverBase {
public:
    virtual void onNotified() = 0;
    virtual ~ObserverBase() = default;
};

class Subject {
public:
    void add(std::shared_ptr<ObserverBase> obs) {
        observers.emplace_back(obs);
    }

    void notifyAll() {
        for (auto it = observers.begin(); it != observers.end(); ) {
            if (auto sp = it->lock()) {
                sp->onNotified();
                ++it;
            } else {
                it = observers.erase(it); // 自动移除已销毁的对象
            }
        }
    }

private:
    std::vector<std::weak_ptr<ObserverBase>> observers;
};

使用者实现:

class UIElement : public ObserverBase {
public:
    void onNotified() override {
        std::cout << "🖥 UI更新!\n";
    }
};

int main() {
    Subject s;

    {
        auto ui = std::make_shared<UIElement>();
        s.add(ui);
        s.notifyAll(); // 输出 🖥 UI更新!
    } // ui 被释放

    s.notifyAll(); // 不输出,不崩溃
}

📌 实战意义:

  • 保证 UI / 控件销毁后不会崩溃
  • 自动清理,避免手动解绑繁琐

✅ 四、进阶功能:支持带参数 / ID 注销 / 一次性订阅

class EventHub {
public:
    using Token = int;
    using Callback = std::function<void(int)>;

    Token subscribe(Callback cb) {
        callbacks[++idCounter] = cb;
        return idCounter;
    }

    void unsubscribe(Token token) {
        callbacks.erase(token);
    }

    void notify(int data) {
        for (auto& [id, cb] : callbacks) {
            cb(data);
        }
    }

private:
    std::unordered_map<Token, Callback> callbacks;
    Token idCounter = 0;
};

示例:

EventHub hub;

int t1 = hub.subscribe([](int d){ std::cout << "用户A收到:" << d << "\n"; });
hub.subscribe([](int d){ std::cout << "用户B收到:" << d << "\n"; });

hub.notify(100);
hub.unsubscribe(t1);
hub.notify(200);

📌 输出:

用户A收到:100
用户B收到:100
用户B收到:200

✅ 五、封装通用模板:ObserverManager

template<typename... Args>
class ObserverManager {
public:
    using Callback = std::function<void(Args...)>;
    using Token = size_t;

    Token connect(Callback cb) {
        observers[++id] = cb;
        return id;
    }

    void disconnect(Token t) {
        observers.erase(t);
    }

    void notify(Args... args) {
        for (auto& [_, cb] : observers) {
            cb(args...);
        }
    }

private:
    std::unordered_map<Token, Callback> observers;
    Token id = 0;
};

应用场景:

  • UI 控件监听尺寸变化:ObserverManager<int width, int height>
  • 网络事件通知:ObserverManager<std::string message>

✅ 六、经验总结:观察者模式设计建议

场景推荐实现方法
简单通知,无解绑vector + lambda
多观察者,需解绑使用 map + ID 管理
避免生命周期崩溃统一使用 weak_ptr + lock
允许观察者自己绑定自身使用 enable_shared_from_this
传参数 / 一次性解绑等拓展使用 template + std::function 封装

📌 小结:观察者模式不止用于 UI,凡是“事件 + 订阅响应”场景,它都是最自然的解决方案。


📘 实战反思:从零构建一个清晰的事件中心

  1. ✅ 用最简结构实现“注册-通知”
  2. ✅ 引入智能指针解决生命周期问题
  3. ✅ 扩展 ID 管理,实现解绑机制
  4. ✅ 模板泛化支持任意参数与事件类型
  5. ✅ 总结设计思维:观察者模式 = 面向通知的编程骨架

🔭 Day 12 预告:信号-槽机制(Qt 式)设计

我们将在下一篇探索更先进的事件系统模型:

  • 模拟 Qt 的 Signal/Slot 信号槽连接
  • 自动绑定对象 + 参数 + 弱引用
  • 一次性触发 + 多路广播 + 分组订阅

如需提前加入 UI 框架、跨线程通知、异步触发等特性,也欢迎你提出建议 ✅

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值