news 2026/4/22 12:00:46

新谈设计模式 Chapter 18 — 观察者模式 Observer

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
新谈设计模式 Chapter 18 — 观察者模式 Observer

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

ObserverMediator
发布者知道订阅者?不知道具体是谁中介知道所有同事
方向单向广播双向协调
耦合极松中介者可能变重
类比公众号推送机场塔台指挥

一句话分清: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);
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/22 11:59:58

从‘噪音’到‘魔法’:手把手图解GSW同态加密的核心思想

从‘噪音’到‘魔法’&#xff1a;手把手图解GSW同态加密的核心思想 想象一下&#xff0c;你有一个神奇的保险箱——不仅能锁住贵重物品&#xff0c;还能让快递员在不开锁的情况下对里面的珠宝进行估价、清洗甚至重新镶嵌。这就是同态加密&#xff08;Homomorphic Encryption, …

作者头像 李华
网站建设 2026/4/22 11:58:35

Windows终极风扇控制指南:5步轻松实现PC散热静音与性能平衡

Windows终极风扇控制指南&#xff1a;5步轻松实现PC散热静音与性能平衡 【免费下载链接】FanControl.Releases This is the release repository for Fan Control, a highly customizable fan controlling software for Windows. 项目地址: https://gitcode.com/GitHub_Trendi…

作者头像 李华
网站建设 2026/4/22 11:58:13

书匠策AI:论文降重与AIGC优化的“双剑合璧”新体验

在学术的广阔天地里&#xff0c;论文写作无疑是一场既考验智慧又考验耐心的马拉松。尤其是面对查重这一大关&#xff0c;许多学者和学生常常感到头疼不已。不过&#xff0c;别担心&#xff0c;今天我要给大家介绍一位论文写作路上的“超级英雄”——书匠策AI&#xff08;书匠策…

作者头像 李华
网站建设 2026/4/22 11:56:34

CompressO视频压缩工具:终极跨平台媒体优化解决方案

CompressO视频压缩工具&#xff1a;终极跨平台媒体优化解决方案 【免费下载链接】compressO Convert any video/image into a tiny size. 100% free & open-source. Available for Mac, Windows & Linux. 项目地址: https://gitcode.com/gh_mirrors/co/compressO …

作者头像 李华
网站建设 2026/4/22 11:54:27

SGM58200 vs. ADS1220:24位ADC选型实战,从数据手册到PCB布局的深度对比

SGM58200 vs. ADS1220&#xff1a;24位ADC选型实战指南 在精密测量系统中&#xff0c;24位模数转换器&#xff08;ADC&#xff09;的选择往往直接决定整个设计的性能上限。面对市面上众多高精度ADC芯片&#xff0c;工程师们常常陷入参数对比的海洋却难以抓住关键差异。本文将以…

作者头像 李华