Chapter 18 — 观察者模式 Observer
灵魂速记:微信公众号——发了文章自动推送给所有关注者,取关了就收不到。
秒懂类比
你关注了一个公众号。公众号发文章时,不需要知道你是谁,只需要把文章推给所有关注者。你想取关?取关之后就不再收到推送了。
发布者不需要知道订阅者的具体类型,只需要知道"有人订阅了,要通知"。
问题引入
// 灾难现场:股票价格变了,要通知一堆人classStock{voidsetPrice(doubleprice){price_=price;// 硬编码通知emailService.send("价格变了: "+std::to_string(price));smsService.send("价格变了: "+std::to_string(price));dashboardUI.update(price);tradingBot.evaluate(price);// 每加一个关注者,改这里……}};模式结构
┌──────────────┐ ┌──────────────┐ │ Subject │────────→│ Observer │ │ (发布者) │ 通知 │ (订阅者) │ ├──────────────┤ 1:N ├──────────────┤ │+subscribe() │ │+update() │ │+unsubscribe()│ └──────────────┘ │+notify() │ └──────────────┘C++ 实现
#include<iostream>#include<memory>#include<string>#include<vector>#include<algorithm>#include<functional>// ========== 观察者接口 ==========classObserver{public:virtual~Observer()=default;virtualvoidonUpdate(conststd::string&event,doubledata)=0;};// ========== 发布者 ==========classStock{public:explicitStock(std::string symbol,doubleprice):symbol_(std::move(symbol)),price_(price){}voidsubscribe(std::shared_ptr<Observer>observer){observers_.push_back(observer);}voidunsubscribe(conststd::shared_ptr<Observer>&observer){observers_.erase(std::remove_if(observers_.begin(),observers_.end(),[&](conststd::weak_ptr<Observer>&wp){autosp=wp.lock();return!sp||sp==observer;}),observers_.end());}voidsetPrice(doubleprice){doubleoldPrice=price_;price_=price;doublechange=((price_-oldPrice)/oldPrice)*100;std::cout<<"📈 "<<symbol_<<": ¥"<<oldPrice<<" → ¥"<<price_<<" ("<<(change>=0?"+":"")<<change<<"%)\n";notify();}conststd::string&symbol()const{returnsymbol_;}doubleprice()const{returnprice_;}private:voidnotify(){// 清理已失效的弱引用 + 通知存活的观察者for(autoit=observers_.begin();it!=observers_.end();){if(autosp=it->lock()){sp->onUpdate(symbol_,price_);++it;}else{it=observers_.erase(it);// 观察者已销毁,清理}}}std::string symbol_;doubleprice_;std::vector<std::weak_ptr<Observer>>observers_;// 用 weak_ptr 而不是 shared_ptr:// - shared_ptr 会让 Stock 持有观察者的所有权,导致观察者无法被销毁(循环引用风险)// - weak_ptr 不影响观察者的生命周期,观察者该销毁就销毁// - 通知时调 lock() 转成 shared_ptr,如果失败说明观察者已死,清理掉即可};// ========== 具体观察者 ==========classPriceDisplay:publicObserver{public:explicitPriceDisplay(std::string name):name_(std::move(name)){}voidonUpdate(conststd::string&symbol,doubleprice)override{std::cout<<" 🖥️ "<<name_<<": "<<symbol<<" 最新价 ¥"<<price<<"\n";}private:std::string name_;};classTradingBot:publicObserver{public:TradingBot(doublebuyBelow,doublesellAbove):buyBelow_(buyBelow),sellAbove_(sellAbove){}voidonUpdate(conststd::string&symbol,doubleprice)override{if(price<buyBelow_){std::cout<<" 🤖 交易机器人: "<<symbol<<" 低于 ¥"<<buyBelow_<<",买入!\n";}elseif(price>sellAbove_){std::cout<<" 🤖 交易机器人: "<<symbol<<" 高于 ¥"<<sellAbove_<<",卖出!\n";}else{std::cout<<" 🤖 交易机器人: "<<symbol<<" 在区间内,持有\n";}}private:doublebuyBelow_,sellAbove_;};intmain(){Stockapple("AAPL",150.0);autodisplay=std::make_shared<PriceDisplay>("主屏幕");autobot=std::make_shared<TradingBot>(140.0,160.0);apple.subscribe(display);apple.subscribe(bot);std::cout<<"=== 价格上涨 ===\n";apple.setPrice(155.0);std::cout<<"\n=== 价格暴涨 ===\n";apple.setPrice(165.0);std::cout<<"\n=== 主屏幕取消订阅 ===\n";apple.unsubscribe(display);apple.setPrice(135.0);}输出:
=== 价格上涨 === 📈 AAPL: ¥150 → ¥155 (+3.33333%) 🖥️ 主屏幕: AAPL 最新价 ¥155 🤖 交易机器人: AAPL 在区间内,持有 === 价格暴涨 === 📈 AAPL: ¥155 → ¥165 (+6.45161%) 🖥️ 主屏幕: AAPL 最新价 ¥165 🤖 交易机器人: AAPL 高于 ¥160,卖出! === 主屏幕取消订阅 === 📈 AAPL: ¥165 → ¥135 (-18.1818%) 🤖 交易机器人: AAPL 低于 ¥140,买入!关键设计决策
用weak_ptr避免循环引用
// ❌ 用 shared_ptr 存观察者 → 观察者永远不会被销毁std::vector<std::shared_ptr<Observer>>observers_;// ✅ 用 weak_ptr → 观察者该死就死,不影响std::vector<std::weak_ptr<Observer>>observers_;线程安全
上面的实现不是线程安全的。如果多个线程同时调用setPrice()和subscribe()/unsubscribe(),会产生数据竞争。多线程场景需要加锁保护observers_的访问:
#include<mutex>classStock{// ...voidsubscribe(std::shared_ptr<Observer>observer){std::lock_guard<std::mutex>lock(mutex_);observers_.push_back(observer);}voidnotify(){std::vector<std::weak_ptr<Observer>>snapshot;{std::lock_guard<std::mutex>lock(mutex_);snapshot=observers_;// 拷贝一份快照}// 用快照通知,避免持锁期间回调导致死锁for(auto&wp:snapshot){if(autosp=wp.lock())sp->onUpdate(symbol_,price_);}}std::mutex mutex_;// ...};生产环境中推荐使用Boost.Signals2或 Qt 的Signal/Slot机制——它们已经处理好了线程安全、取消订阅、连接管理等所有细节。
什么时候用?
| ✅ 适合 | ❌ 别用 |
|---|---|
| 一个对象变化要通知多个对象 | 只有一对一的通知 |
| 订阅者数量和类型动态变化 | 订阅关系固定 |
| 发布者不应该知道订阅者细节 | 需要同步回调结果 |
| 事件驱动系统、GUI、消息总线 | 简单的函数调用就行 |
防混淆
Observer vs Mediator
| Observer | Mediator | |
|---|---|---|
| 发布者知道订阅者? | 不知道具体是谁 | 中介知道所有同事 |
| 方向 | 单向广播 | 双向协调 |
| 耦合 | 极松 | 中介者可能变重 |
| 类比 | 公众号推送 | 机场塔台指挥 |
一句话分清:Observer 是广播不问听众,Mediator 是指挥认识所有人。
Observer vs 事件/信号
现代 C++ 中,Observer 经常被 signal/slot 或事件系统替代:
// 用 std::function 实现轻量事件系统#include<functional>#include<vector>classEventEmitter{public:usingHandler=std::function<void(double)>;voidon(Handler h){handlers_.push_back(std::move(h));}voidemit(doublevalue){for(auto&h:handlers_)h(value);}private:std::vector<Handler>handlers_;};// 用起来更简洁EventEmitter priceChanged;priceChanged.on([](doublep){std::cout<<"价格: "<<p<<"\n";});priceChanged.on([](doublep){if(p>100)std::cout<<"贵了!\n";});priceChanged.emit(120.0);