設計模式 | 觀察者模式

觀察者模式(Observer Pattern)是行為型設計模式中的事件通知專家,它定義了對象間一種一對多的依賴關系,當一個對象狀態改變時,所有依賴它的對象都會自動收到通知并更新。這種模式實現了發布-訂閱機制,是事件處理系統的核心基礎。本文將深入探索觀察者模式的核心思想、實現技巧以及在C++中的高效實踐。


為什么需要觀察者模式?

在軟件開發中,對象狀態變化需要通知其他對象:

  • GUI中的按鈕點擊事件處理

  • 股票價格變動通知交易系統

  • 游戲引擎中的碰撞檢測通知

  • 分布式系統中的服務狀態更新

  • 物聯網設備狀態同步

直接的對象間通知會導致:
緊耦合:對象間存在復雜依賴關系
維護困難:添加/刪除觀察者需修改主題代碼
性能問題:同步通知阻塞主線程
擴展性差:難以動態添加新觀察者類型

觀察者模式通過解耦主題和觀察者解決了這些問題。


觀察者模式的核心概念

模式結構解析
[主題] ← [具體主題]|| 通知▼
[觀察者] ← [具體觀察者]
關鍵角色定義
  1. 主題接口(Subject)

    • 提供注冊、刪除和通知觀察者的接口

    • 維護觀察者列表

  2. 具體主題(Concrete Subject)

    • 實現主題接口

    • 存儲狀態數據

    • 狀態變化時通知觀察者

  3. 觀察者接口(Observer)

    • 定義更新接口

    • 接收主題通知

  4. 具體觀察者(Concrete Observer)

    • 實現觀察者接口

    • 維護對主題的引用

    • 同步自身狀態與主題狀態


C++實現:股票交易通知系統

實現一個股票價格變動通知系統,展示觀察者模式的實際應用:

#include <iostream>
#include <string>
#include <vector>
#include <memory>
#include <unordered_map>
#include <algorithm>
#include <mutex>
#include <thread>
#include <chrono>
#include <random>// ================= 觀察者接口 =================
class StockObserver {
public:virtual ~StockObserver() = default;virtual void update(const std::string& symbol, double price) = 0;virtual std::string getName() const = 0;
};// ================= 主題接口 =================
class StockMarket {
public:virtual ~StockMarket() = default;virtual void registerObserver(const std::string& symbol, std::shared_ptr<StockObserver> observer) = 0;virtual void removeObserver(const std::string& symbol, const std::string& observerName) = 0;virtual void notifyObservers(const std::string& symbol) = 0;virtual void setPrice(const std::string& symbol, double price) = 0;virtual double getPrice(const std::string& symbol) const = 0;
};// ================= 具體主題:股票交易所 =================
class StockExchange : public StockMarket {
public:void registerObserver(const std::string& symbol, std::shared_ptr<StockObserver> observer) override {std::lock_guard lock(mutex_);observers_[symbol].push_back(observer);std::cout << observer->getName() << " 注冊監聽股票: " << symbol << std::endl;}void removeObserver(const std::string& symbol, const std::string& observerName) override {std::lock_guard lock(mutex_);auto& list = observers_[symbol];list.erase(std::remove_if(list.begin(), list.end(),[&](const auto& obs) {return obs->getName() == observerName;}),list.end());std::cout << observerName << " 取消監聽股票: " << symbol << std::endl;}void notifyObservers(const std::string& symbol) override {std::vector<std::shared_ptr<StockObserver>> currentObservers;{std::lock_guard lock(mutex_);if (observers_.find(symbol) == observers_.end()) return;currentObservers = observers_[symbol];}double currentPrice = getPrice(symbol);for (auto& observer : currentObservers) {observer->update(symbol, currentPrice);}}void setPrice(const std::string& symbol, double price) override {{std::lock_guard lock(mutex_);stockPrices_[symbol] = price;}std::cout << "\n=== " << symbol << " 價格更新: " << price << " ===" << std::endl;notifyObservers(symbol);}double getPrice(const std::string& symbol) const override {std::lock_guard lock(mutex_);auto it = stockPrices_.find(symbol);return it != stockPrices_.end() ? it->second : 0.0;}private:mutable std::mutex mutex_;std::unordered_map<std::string, double> stockPrices_;std::unordered_map<std::string, std::vector<std::shared_ptr<StockObserver>>> observers_;
};// ================= 具體觀察者:交易機器人 =================
class TradingBot : public StockObserver {
public:TradingBot(const std::string& name, double buyThreshold, double sellThreshold): name_(name), buyThreshold_(buyThreshold), sellThreshold_(sellThreshold) {}void update(const std::string& symbol, double price) override {std::cout << "🤖 " << name_ << " 收到 " << symbol << " 更新: " << price << std::endl;// 交易決策邏輯if (price <= buyThreshold_ && !holding_) {buy(symbol, price);} else if (price >= sellThreshold_ && holding_) {sell(symbol, price);}}std::string getName() const override { return name_; }private:void buy(const std::string& symbol, double price) {std::cout << "買入 " << symbol << " @ " << price << std::endl;holding_ = true;lastBuyPrice_ = price;}void sell(const std::string& symbol, double price) {double profit = price - lastBuyPrice_;std::cout << "賣出 " << symbol << " @ " << price << " | 利潤: " << profit << std::endl;holding_ = false;}std::string name_;double buyThreshold_;double sellThreshold_;bool holding_ = false;double lastBuyPrice_ = 0.0;
};// ================= 具體觀察者:價格警報器 =================
class PriceAlert : public StockObserver {
public:PriceAlert(const std::string& name, double alertPrice, bool above): name_(name), alertPrice_(alertPrice), above_(above) {}void update(const std::string& symbol, double price) override {if ((above_ && price >= alertPrice_) || (!above_ && price <= alertPrice_)) {std::cout << name_ << " 警報: " << symbol << " 價格 " << (above_ ? "突破" : "跌破") << " " << alertPrice_ << " (當前: " << price << ")" << std::endl;}}std::string getName() const override { return name_; }private:std::string name_;double alertPrice_;bool above_;
};// ================= 具體觀察者:移動應用通知 =================
class MobileApp : public StockObserver {
public:explicit MobileApp(const std::string& user) : user_(user) {}void update(const std::string& symbol, double price) override {std::cout << "[" << user_ << "] 股票更新: " << symbol << " 當前價格: " << price << std::endl;}std::string getName() const override { return "MobileApp-" + user_; }private:std::string user_;
};// ================= 股票價格模擬器 =================
class StockPriceSimulator {
public:StockPriceSimulator(StockMarket& market, const std::string& symbol, double startPrice, double volatility): market_(market), symbol_(symbol), currentPrice_(startPrice), volatility_(volatility), running_(false) {}void start() {running_ = true;thread_ = std::thread(&StockPriceSimulator::run, this);}void stop() {running_ = false;if (thread_.joinable()) thread_.join();}void run() {std::random_device rd;std::mt19937 gen(rd());std::normal_distribution<> dist(0.0, volatility_);while (running_) {// 隨機波動double change = dist(gen);currentPrice_ += currentPrice_ * change;// 確保價格不為負if (currentPrice_ < 0.1) currentPrice_ = 0.1;// 更新市場market_.setPrice(symbol_, currentPrice_);// 暫停std::this_thread::sleep_for(std::chrono::seconds(1));}}private:StockMarket& market_;std::string symbol_;double currentPrice_;double volatility_;bool running_;std::thread thread_;
};// ================= 客戶端代碼 =================
int main() {// 創建股票交易所auto exchange = std::make_shared<StockExchange>();// 初始化股票價格exchange->setPrice("AAPL", 150.0);exchange->setPrice("GOOGL", 2800.0);exchange->setPrice("TSLA", 700.0);// 創建觀察者auto bot1 = std::make_shared<TradingBot>("保守機器人", 145.0, 160.0);auto bot2 = std::make_shared<TradingBot>("激進機器人", 155.0, 170.0);auto alert1 = std::make_shared<PriceAlert>("AAPL警報", 160.0, true);auto alert2 = std::make_shared<PriceAlert>("TSLA警報", 750.0, true);auto mobileUser1 = std::make_shared<MobileApp>("張三");auto mobileUser2 = std::make_shared<MobileApp>("李四");// 注冊觀察者exchange->registerObserver("AAPL", bot1);exchange->registerObserver("AAPL", bot2);exchange->registerObserver("AAPL", alert1);exchange->registerObserver("AAPL", mobileUser1);exchange->registerObserver("GOOGL", mobileUser1);exchange->registerObserver("TSLA", alert2);exchange->registerObserver("TSLA", mobileUser2);// 啟動價格模擬器StockPriceSimulator aaplSimulator(*exchange, "AAPL", 150.0, 0.02);StockPriceSimulator tslaSimulator(*exchange, "TSLA", 700.0, 0.03);aaplSimulator.start();tslaSimulator.start();// 模擬運行一段時間std::this_thread::sleep_for(std::chrono::seconds(10));// 動態添加新觀察者std::cout << "\n===== 添加新觀察者 =====" << std::endl;auto newAlert = std::make_shared<PriceAlert>("AAPL下跌警報", 140.0, false);exchange->registerObserver("AAPL", newAlert);// 繼續運行std::this_thread::sleep_for(std::chrono::seconds(5));// 停止模擬aaplSimulator.stop();tslaSimulator.stop();// 移除部分觀察者std::cout << "\n===== 移除觀察者 =====" << std::endl;exchange->removeObserver("AAPL", "激進機器人");exchange->removeObserver("TSLA", "MobileApp-李四");return 0;
}

觀察者模式的五大優勢

  1. 解耦主題與觀察者

    // 主題不知道觀察者具體類型
    void notifyObservers() {for (auto& obs : observers_) {obs->update(data); // 僅依賴接口}
    }
  2. 動態添加觀察者

    // 運行時注冊新觀察者
    exchange->registerObserver("AAPL", newTradingBot);
  3. 支持廣播通信

    // 通知所有訂閱者
    void setPrice(const string& symbol, double price) {stockPrices_[symbol] = price;notifyObservers(symbol); // 廣播通知
    }
  4. 事件驅動架構

    // 響應狀態變化
    void onPriceChange() {market_->setPrice(symbol_, newPrice); // 觸發事件
    }
  5. 多播通信控制

    // 按主題通知
    void notifyObservers(const string& symbol) {// 只通知訂閱該symbol的觀察者
    }

觀察者模式的高級應用

1. 事件總線系統
class EventBus {
public:void subscribe(const string& eventType, shared_ptr<Observer> observer) {subscribers_[eventType].push_back(observer);}void unsubscribe(const string& eventType, const string& observerName) {auto& list = subscribers_[eventType];list.erase(remove_if(list.begin(), list.end(),[&](auto& obs) { return obs->getName() == observerName; }), list.end());}void publish(const string& eventType, const string& data) {if (subscribers_.find(eventType) == subscribers_.end()) return;for (auto& observer : subscribers_[eventType]) {observer->handleEvent(eventType, data);}}
};
2. 異步觀察者
class AsyncNotifier {
public:void notify(shared_ptr<Observer> observer, const string& data) {queue_.push({observer, data});}void processNotifications() {while (!queue_.empty()) {auto [observer, data] = queue_.front();queue_.pop();observer->update(data);}}private:queue<pair<shared_ptr<Observer>, string>> queue_;
};class AsyncStockExchange : public StockExchange {void notifyObservers(const string& symbol) override {double price = getPrice(symbol);for (auto& observer : getObservers(symbol)) {asyncNotifier_.notify(observer, symbol, price);}}void processAsyncNotifications() {asyncNotifier_.processNotifications();}
};
3. 觀察者優先級
class PriorityStockExchange : public StockExchange {void registerObserver(const string& symbol, shared_ptr<StockObserver> observer,int priority) {auto& list = observers_[symbol];list.insert(std::upper_bound(list.begin(), list.end(), priority, [](int prio, const auto& obs) { return prio > obs->getPriority(); }), observer);}void notifyObservers(const string& symbol) override {auto observers = getObservers(symbol);for (auto& observer : observers) {observer->update(symbol, getPrice(symbol));}}
};

觀察者模式的應用場景

1. GUI事件處理
class Button {
public:void addClickListener(shared_ptr<ClickListener> listener) {clickListeners_.push_back(listener);}void click() {// UI邏輯...notifyClickListeners();}private:void notifyClickListeners() {for (auto& listener : clickListeners_) {listener->onClick(this);}}vector<shared_ptr<ClickListener>> clickListeners_;
};class LoginDialog {
public:LoginDialog() {loginButton_->addClickListener(make_shared<ClickListener>([this] {attemptLogin();}));}void attemptLogin() {// 登錄邏輯...}
};
2. 游戲引擎事件系統
class GameEventSystem {
public:void subscribe(EventType type, shared_ptr<GameObject> obj) {subscribers_[type].push_back(obj);}void publish(EventType type, EventData data) {for (auto& subscriber : subscribers_[type]) {subscriber->handleEvent(type, data);}}
};class Player : public GameObject {void handleEvent(EventType type, EventData data) override {if (type == EventType::COLLISION) {auto& collData = static_cast<CollisionData&>(data);if (collData.other->isEnemy()) {takeDamage(collData.damage);}}}
};class PhysicsEngine {void detectCollision() {// 碰撞檢測...eventSystem_.publish(EventType::COLLISION, collisionData);}
};
3. 配置變更通知
class ConfigManager {
public:void addChangeListener(shared_ptr<ConfigChangeListener> listener) {listeners_.push_back(listener);}void setValue(const string& key, const string& value) {config_[key] = value;notifyChangeListeners(key, value);}private:void notifyChangeListeners(const string& key, const string& value) {for (auto& listener : listeners_) {listener->onConfigChange(key, value);}}unordered_map<string, string> config_;vector<shared_ptr<ConfigChangeListener>> listeners_;
};class ThemeManager : public ConfigChangeListener {void onConfigChange(const string& key, const string& value) override {if (key == "theme") {applyTheme(value);}}
};

觀察者模式與其他模式的關系

模式關系區別
中介者都管理對象間通信中介者集中控制,觀察者分布通知
發布-訂閱觀察者的高級變體發布-訂閱通常解耦更強
責任鏈都處理請求責任鏈傳遞請求,觀察者廣播通知
備忘錄都可實現狀態通知備忘錄保存狀態,觀察者響應狀態變化

組合使用示例:

// 觀察者 + 中介者
class ChatRoomMediator : public Mediator {void sendMessage(const string& msg, User* sender) override {for (auto user : users_) {if (user != sender) {user->receive(msg); // 觀察者通知}}}
};

觀察者模式的挑戰與解決方案

挑戰解決方案
觀察者執行阻塞使用異步通知或線程池
通知順序依賴實現優先級隊列
內存泄漏風險使用弱引用或自動注銷機制
意外遞歸更新實現更新標記和批處理

弱引用觀察者示例

class WeakObserver : public StockObserver {
public:void registerWith(weak_ptr<StockMarket> market) {market_ = market;}void update(const string& symbol, double price) override {if (auto market = market_.lock()) {// 安全訪問}}private:weak_ptr<StockMarket> market_;
};

總結

觀察者模式是現代軟件架構的事件通信基石,它通過:

  • 解耦設計:分離事件生產者和消費者

  • 動態訂閱:運行時添加/移除觀察者

  • 事件廣播:高效通知多個訂閱者

  • 響應式編程:支持事件驅動架構

適用場景

  • 需要實現事件通知機制

  • 需要解耦事件源和事件處理器

  • 需要廣播狀態變化

  • 需要動態管理訂閱關系

"觀察者模式不是簡單的回調機制,而是將事件處理提升為系統架構。它是響應式系統的通信生命線。" — 設計模式實踐者

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/87710.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/87710.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/87710.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Apache Struts2 遠程命令執行漏洞(S2-052)

一、漏洞概述 S2-052 是 Apache Struts2 框架中一個高危的遠程代碼執行漏洞&#xff08;CVE-2017-9805&#xff09;&#xff0c;由安全研究人員于 2017 年發現并公開。該漏洞源于 Struts2 的 REST 插件在使用 XStream 組件處理 XML 反序列化時&#xff0c;未對用戶輸入的 XML 數…

RS觸發器Multisim電路仿真——硬件工程師筆記

目錄 1 RS觸發器基礎知識 1.1 工作原理 1.2 電路結構 1.3 特點 1.4 應用 1.5 設計考慮 1.6 總結 2 與非門實現基本RS觸發器 2.1 電路結構 2.2 工作原理 2.3 特點 2.4 總結 3 或非門實現基本RS觸發器 3.1 電路結構 3.2 工作原理 3.3 特點 3.4 總結 4 與非門實…

提示技術系列(12)——程序輔助語言模型

什么是提示技術&#xff1f; 提示技術是實現提示工程目標的具體技術手段&#xff0c;是提示工程中的“工具庫”。 什么又是提示工程&#xff1f; 提示工程是指通過設計、優化和迭代輸入到大語言模型&#xff08;LLM&#xff09;的提示&#xff08;Prompt&#xff09;&#xff…

明遠智睿H618:開啟多場景智慧生活新時代

在數字化浪潮的推動下&#xff0c;智能設備正深刻地改變著我們的生活方式。明遠智睿H618以其強大的功能和卓越的性能&#xff0c;在家庭娛樂、商業展示、教育培訓和智能家居控制等多個領域展現出巨大的應用潛力&#xff0c;開啟了多場景智慧生活的新時代。 家庭娛樂&#xff1…

探秘展銷編輯器:相較于傳統展銷的卓越優勢與甄選指南?

在競爭激烈的商業環境中&#xff0c;企業期望通過展銷活動提升品牌知名度、推廣產品和拓展市場&#xff0c;但傳統展銷方式存在諸多難題。一是場地限制&#xff0c;優質場地稀缺、租金貴、檔期緊&#xff0c;場地空間和布局也不一定合適;二是展示形式單一&#xff0c;多為靜態展…

第31篇:塊設備與字符設備管理深度解析(基于OpenEuler 24.03)

塊設備與字符設備管理深度解析&#xff08;基于OpenEuler 24.03&#xff09; 文章目錄 塊設備與字符設備管理深度解析&#xff08;基于OpenEuler 24.03&#xff09;一、設備基礎概念體系1.1 塊設備的核心特性與分類1.2 字符設備的流式數據模型1.3 設備標識系統&#xff1a;主設…

Django Channels WebSocket實時通信實戰:從聊天功能到消息推送

引言 在Web開發中&#xff0c;實時通信功能&#xff08;如在線聊天、實時通知、數據推送&#xff09;已成為許多應用的核心需求。傳統的HTTP協議由于其請求-響應模式的限制&#xff0c;無法高效實現實時通信。WebSocket作為一種全雙工通信協議&#xff0c;為實時Web應用提供了…

day52 神經網絡調參指南

目錄 隨機種子 內參的初始化 神經網絡調參指南 參數的分類 調參順序 初始化參數 batchsize的選擇 學習率調整 激活函數的選擇 損失函數的選擇 模型架構中的參數 正則化系數 其他補充 隨機種子 import torch import torch.nn as nn# 定義簡單的線性模型&#xf…

.NET9 實現斐波那契數列(FibonacciSequence)性能測試

在 .NET 平臺上實現 斐波那契數列 并使用 BenchmarkDotNet 進行性能測試&#xff0c;是評估不同算法實現方式性能表現的一種高效且標準化的方法。通過該方式&#xff0c;可以對比遞歸、迭代、記憶化遞歸以及結合高性能優化技術&#xff08;如 Span<T>、Memory<T> 和…

三、docker軟件安裝:gitlab,nexus,mysql8,redis,nacos,nginx

目錄 1.gitlab安裝 2.nexus安裝 (1)下載啟動 (2)設置中央倉庫遠程地址 (3)配置maven的settings.xml 3.mysql8安裝 4.redis安裝 5.nacos安裝 6.nginx安裝 1.gitlab安裝 #創建目錄 cd /usr/local/ mkdir docker cd docker/ mkdir gitlab_docker cd gitlab_docker…

【與AI+】SAP WEBGUI集成開發與SAP INTERNET服務的關系

前言&#xff1a;這是我的水水專欄第五篇文章&#xff0c;這個專欄呢&#xff0c;是放一些我向AI提問的問題&#xff0c;以及AI的回答。因為感覺真的好方便哈哈哈~ 我不是很確定我的專欄文章內容是否涉及版權&#xff0c;以及也不確定這些整合過的文字是否涉嫌抄襲&#xff0c…

淺談幾種js設計模式

JavaScript設計模式是開發中常用的一種解決方案&#xff0c;它們幫助開發者以一種更結構化、更易維護的方式編寫代碼。本文將深入介紹幾種常見的JavaScript設計模式&#xff0c;包括單例模式、工廠模式、觀察者模式和策略模式。 一、單例模式&#xff08;Singleton Pattern&am…

手寫 Vue 中虛擬 DOM 到真實 DOM 的完整過程

目錄 一、虛擬 DOM 的核心概念 二、虛擬 DOM 到真實 DOM 的流程 三、手寫虛擬 DOM 到真實 DOM 的實現 1. 定義虛擬 DOM 的結構&#xff08;VNode&#xff09; 2. 創建虛擬 DOM 轉真實 DOM 的函數 3. 掛載虛擬 DOM 到頁面 4. 更新虛擬 DOM 的過程&#xff08;Diff 算法簡化…

jmm--volatile

指令重排基礎概念 在現代處理器和編譯器為了提高程序執行效率&#xff0c;會對指令進行優化&#xff0c;其中一種優化方式就是指令重排序。在單線程環境下&#xff0c;指令重排序不會影響最終執行結果&#xff0c;因為處理器和編譯器會保證重排序后的執行結果與按照代碼順序執行…

【硬件開發】濾波電容的選擇:原理、計算與多電壓值應用實踐

濾波電容的選擇&#xff1a;原理、計算與多電壓值應用實踐 1. 引言 在現代電子系統中&#xff0c;穩定的電源供應是保證電路可靠運行的基礎。然而&#xff0c;電源線上往往不可避免地存在各種噪聲和紋波&#xff0c;這些干擾可能源自電源本身&#xff08;如整流后的脈動直流&…

【seismic unix數據生成-unif2】

Seismic Unix簡介 Seismic Unix&#xff08;SU&#xff09;是由科羅拉多礦業學院&#xff08;Colorado School of Mines&#xff09;開發的開源地震數據處理軟件包&#xff0c;專為地震勘探數據分析和研究設計。它提供了一系列命令行工具&#xff0c;支持從數據加載、處理到可…

【逆向思考 并集查找】P2391 白雪皚皚|省選-

本文涉及知識點 C并集查找 P2391 白雪皚皚 題目背景 “柴門聞犬吠&#xff0c;風雪夜歸人”&#xff0c;冬天&#xff0c;不期而至。千里冰封&#xff0c;萬里雪飄。空中刮起了鴨毛大雪。雪花紛紛&#xff0c;降落人間。 美能量星球&#xff08;pty 在 spore 上的一個殖民地…

一文講清楚React中setState的使用方法和機制

文章目錄 一文講清楚React中setState的使用方法和機制1. setState是什么2. setState方法詳解2.1 setState參數詳解2.2 setState同步異步問題2.2.1 setState異步更新2.2.2 setState同步更新 一文講清楚React中setState的使用方法和機制 1. setState是什么 React中&#xff0c;…

01_軟件卓越之道:功能性與需求滿足

引言 在軟件的世界里&#xff0c;功能性是產品與用戶之間的第一橋梁。一個軟件即使擁有華麗的界面和極致的性能&#xff0c;如果不能解決用戶的核心需求&#xff0c;也終將被市場淘汰。本文將深入探討如何確保軟件的功能性與用戶需求完美契合。 1. 需求理解&#xff1a;從模糊…

StarRocks × Tableau 連接器完整使用指南 | 高效數據分析從連接開始

一、導語&#xff1a;為什么選擇 StarRocks Tableau 連接器&#xff1f; 在當今數據驅動的商業環境中&#xff0c;企業不僅需要一個能夠處理海量數據的高性能分析數據庫&#xff0c;還需要一個直觀、強大的可視化工具來解讀數據背后的故事。StarRocks 作為新一代極速全場景 MP…