C++設計模式(GOF-23)——03 C++觀察者模式(Observer / Event、發布-訂閱模式、事件模式)

文章目錄

  • 一、觀察者模式概述
  • 二、傳統代碼 vs 觀察者模式對比
    • 1. 傳統實現(緊耦合)
    • 2. 觀察者模式實現(松耦合)
  • 三、Mermaid 類圖說明
  • 四、核心設計要點
    • 1. 接口分層設計
    • 2. 通知機制實現
    • 3. 擴展性驗證
  • 五、應用場景與注意事項
    • 適用場景
    • 注意事項
      • 1. 內存管理:需注意觀察者生命周期管理,避免野指針
      • 2. 通知順序:觀察者列表順序可能影響業務邏輯
      • 3. 線程安全:多線程環境下需考慮同步機制
      • 4. 異常處理:單個觀察者異常不應影響整體通知流程
  • 六、現代C++改進方案
  • 七、總結

一、觀察者模式概述

觀察者模式(Observer Pattern)是一種行為型設計模式,用于建立對象間的一對多依賴關系。當一個對象(被觀察者)狀態發生變化時,所有依賴它的對象(觀察者)都會自動收到通知并更新。這種模式通過解耦通知方與接收方,實現了系統的靈活性和可擴展性。


二、傳統代碼 vs 觀察者模式對比

1. 傳統實現(緊耦合)

// 抽象顯示元素接口,強制子類實現 display 方法
class DisplayElement {
public:virtual void display() = 0;
};// 具體顯示類:直接綁定 WeatherData 數據
class CurrentConditionsDisplay : public DisplayElement {
public:// 接收 WeatherData 的更新數據并刷新顯示void update(float temp, float humidity, float pressure) {this->temp = temp;this->humidity = humidity;this->pressure = pressure;display();  // 調用 display 展示最新數據}void display() override {std::cout << "Current conditions: " << temp << "F degrees and " << humidity << "% humidity" << std::endl;}private:float temp, humidity, pressure;
};// 被觀察者類:直接持有顯示對象(緊耦合)
class WeatherData {
public:// 設置天氣數據并直接觸發顯示更新void setMeasurements(float temp, float humidity, float pressure) {this->temp = temp;this->humidity = humidity;this->pressure = pressure;// ??直接調用具體觀察者方法,形成硬編碼依賴currentDisplay.update(temp, humidity, pressure);}private:CurrentConditionsDisplay currentDisplay;  // ??硬編碼依賴具體觀察者float temp, humidity, pressure;
};

問題分析:

  • 緊耦合問題WeatherData 需要直接包含具體觀察者類(如 CurrentConditionsDisplay
  • 擴展困難:添加新觀察者需要修改 WeatherData
  • 違反開閉原則:系統難以對擴展開放,對修改關閉

2. 觀察者模式實現(松耦合)

// 抽象觀察者接口:定義統一的更新協議
class IObserver {
public:// 純虛函數:子類必須實現 update 方法virtual void update(float temp, float humidity, float pressure) = 0;virtual ~IObserver() = default;  // 虛析構確保多態釋放
};// 抽象被觀察者接口:定義觀察者管理操作
class ISubject {
public:// 注冊觀察者virtual void registerObserver(IObserver* observer) = 0;// 移除觀察者virtual void removeObserver(IObserver* observer) = 0;// 通知所有觀察者virtual void notifyObservers() = 0;
};// 具體被觀察者實現
class WeatherData : public ISubject {
public:// 實現注冊觀察者方法void registerObserver(IObserver* observer) override {observers.push_back(observer);  // 將觀察者加入列表}// 實現移除觀察者方法void removeObserver(IObserver* observer) override {// 使用 std::remove 重排容器,然后 erase 刪除observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end());}// 實現通知觀察者方法void notifyObservers() override {for (auto& observer : observers) {observer->update(temp, humidity, pressure);  // 通過接口調用更新}}// 設置天氣數據并觸發通知void setMeasurements(float temp, float humidity, float pressure) {this->temp = temp;this->humidity = humidity;this->pressure = pressure;measurementsChanged();  // 觸發狀態變化通知}private:// 狀態變化時調用通知邏輯void measurementsChanged() {notifyObservers();  // 通知所有注冊觀察者}std::vector<IObserver*> observers;  // 存儲觀察者指針列表float temp, humidity, pressure;
};// 具體觀察者實現
class CurrentConditionsDisplay : public IObserver {
public:// 構造函數自動注冊到 WeatherDataCurrentConditionsDisplay(WeatherData& weatherData) {this->weatherData.registerObserver(this);  // 將自身注冊為觀察者}// 實現 update 方法void update(float temp, float humidity, float pressure) override {this->temp = temp;this->humidity = humidity;this->pressure = pressure;display();  // 刷新顯示}void display() {std::cout << "Current conditions: " << temp << "F degrees and " << humidity << "% humidity" << std::endl;}private:float temp, humidity, pressure;WeatherData& weatherData;  // 引用 WeatherData 實例
};

(一堆觀察者,一堆被觀察者,一堆接口;被觀察者提供觀察者注冊、解綁、通知接口,被觀察者提供數據更新并通知方法;觀察者實現被觀察者注冊構造方法,實現更新接口,實現展示方法)

改進優勢:

  • 松耦合WeatherData 只依賴抽象接口 IObserver
  • 可擴展性強:新增觀察者只需實現 IObserver 接口
  • 符合開閉原則:無需修改 WeatherData 即可擴展功能

三、Mermaid 類圖說明

1
n
uses
ISubject
+registerObserver(observer: IObserver) : void
+removeObserver(observer: IObserver) : void
+notifyObservers() : void
IObserver
+update(temp: float, humidity: float, pressure: float) : void
WeatherData
-observers: std::vector
+setMeasurements(temp: float, humidity: float, pressure: float) : void
CurrentConditionsDisplay
+display() : void

四、核心設計要點

1. 接口分層設計

  • ISubject 接口:定義觀察者管理方法(注冊/移除/通知)
  • IObserver 接口:定義更新方法的標準化協議
  • 具體實現類:通過組合方式實現接口協作

2. 通知機制實現

void WeatherData::notifyObservers() {for (auto& observer : observers) {observer->update(temp, humidity, pressure);  // 通過接口調用具體實現}
}
  • 使用迭代器遍歷觀察者列表
  • 通過統一接口調用更新方法
  • 實現"廣播-訂閱"通信模式

3. 擴展性驗證

新增統計顯示觀察者只需:

class StatisticsDisplay : public IObserver {
public:void update(float temp, float humidity, float pressure) override {temps.push_back(temp);// 計算統計信息...display();}void display() {std::cout << "Stats: Max/Min/Avg temperature..." << std::endl;}
private:std::vector<float> temps;
};

無需修改 WeatherData 類即可實現功能擴展


五、應用場景與注意事項

適用場景

  • GUI 事件處理系統(如按鈕點擊事件)
  • 實時數據監控系統
  • 分布式事件總線架構
  • 游戲中的事件驅動系統

注意事項

1. 內存管理:需注意觀察者生命周期管理,避免野指針

2. 通知順序:觀察者列表順序可能影響業務邏輯

3. 線程安全:多線程環境下需考慮同步機制

4. 異常處理:單個觀察者異常不應影響整體通知流程


六、現代C++改進方案

使用智能指針和lambda表達式增強安全性:

class ModernWeatherData : public ISubject {
public:void registerObserver(std::shared_ptr<IObserver> observer) override {observers.push_back(observer);  // 使用智能指針避免內存泄漏}// ...其他方法...
private:std::vector<std::shared_ptr<IObserver>> observers;  // 智能指針容器
};

七、總結

特性傳統實現觀察者模式
對象耦合度
擴展性需修改現有代碼支持開閉原則
維護成本
通知靈活性固定調用動態注冊/注銷
適用場景復雜度簡單場景復雜系統架構

觀察者模式通過接口抽象和行為封裝,為復雜系統提供了優雅的通信解決方案。在C++中實現時需注意接口設計規范、內存管理和線程安全等工程實踐問題,合理使用智能指針和現代C++特性可進一步提升代碼質量。

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

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

相關文章

海外 AI 部署:中國出海企業如何選擇穩定、安全的云 GPU 基礎設施?

2025年&#xff0c;中國 AI 企業在模型訓練、產品落地和創新應用上不斷刷新人們的認知。DeepSeek-R1、Qwen3 等國產大模型密集亮相&#xff0c;國內大模型產業熱潮持續升溫。與此同時&#xff0c;一個現實的問題也在被越來越多企業關注&#xff1a;模型雖然訓練得起&#xff0c…

AI繪畫工具實測:Stable Diffusion本地部署指

對于想要深度體驗AI繪畫的創作者來說&#xff0c;本地部署Stable Diffusion能帶來更自由的創作空間。本文將詳細介紹Windows系統下的部署流程&#xff0c;幫助你在個人電腦上搭建專業的AI繪畫環境。 硬件準備與基礎環境配置 部署前需確認電腦配置&#xff1a;建議NVIDIA顯卡&…

macOS - 快速上手使用 YOLO

文章目錄 一、關于 yolo二、安裝三、命令行使用官方示例yolo cfgyolo predict 四、Python 調用results 數據 一、關于 yolo YOLO(YOLO&#xff08;You Only Look Once&#xff09;是一種流行的物體檢測和圖像分割模型&#xff0c;由華盛頓大學的約瑟夫-雷德蒙&#xff08;Jose…

<script setup> 語法糖

下面&#xff0c;我們來系統的梳理關于 Vue 3 <script setup> 語法糖 的基本知識點&#xff1a; 一、<script setup> 核心概念 1.1 什么是 <script setup>&#xff1f; <script setup> 是 Vue 3 中 Composition API 的編譯時語法糖&#xff0c;它通過…

MYSQL-InnoDB邏輯存儲結構 詳解

InnoDB邏輯存儲結構 段—區—頁—行 表空間&#xff1a; 默認情況下InnoDB有一個共享表空間ibdata1&#xff0c;所有數據放入這個表空間&#xff0c;如果開啟了innodb_file_per_table&#xff08;默認ON&#xff09;&#xff0c;每張表都可以放到一個單獨的表空間&#xff0…

[特殊字符] Python 批量合并 Word 表格中重復單元格教程(收貨記錄案例實戰)

在日常辦公中&#xff0c;Word 表格中常出現重復的“供應商名稱”或“物料編碼”&#xff0c;會導致表格冗余且視覺混亂。這時候&#xff0c;用 Python 自動合并重復單元格可以大幅提升表格專業度和可讀性。本篇給大家演示如何用 python-docx 實現該功能。 ? 功能概覽 自動讀取…

從零構建Node.js服務托管前端項目

下面是一個完整的指南&#xff0c;教你如何從零開始構建一個Node.js服務來托管前端項目&#xff0c;并代理API請求到其他服務器。 1. 項目初始化 # 創建項目目錄 mkdir node-proxy-server cd node-proxy-server# 初始化npm項目 npm init -y# 安裝必要依賴 npm install expres…

Lynx vs React Native vs Flutter 全面對比:三大跨端框架實測分析

一文看懂三大熱門跨端技術的歷史淵源、架構機制、開發體驗、包體積對比與性能評估。 我陪你用實測數據帶你理性選型&#xff0c;不踩坑&#xff0c;不盲信。 1. 框架簡介&#xff1a;它們是誰&#xff1f;來自哪里&#xff1f;干嘛用&#xff1f; 框架名稱所屬公司發布時間初衷…

CKESC的ROCK 180A-H 無人機電調:100V 高壓冗余設計與安全保護解析

一、核心技術參數與性能指標 電壓范圍&#xff1a;支持 12~26S 鋰電&#xff08;適配 110V 高壓系統&#xff09;電流特性&#xff1a; 持續工作電流&#xff1a;90A&#xff08;特定散熱條件&#xff09;瞬時耐流&#xff08;1 秒&#xff09;&#xff1a;220A&#xff0c;3 …

優化 ArcPy 腳本性能

使用并行處理 如果硬件條件允許&#xff0c;可以使用 Python 的并行處理模塊&#xff08;如 multiprocessing&#xff09;來同時處理多個小任務。這樣可以充分利用多核處理器的優勢&#xff0c;提高腳本的執行效率。 import multiprocessing def process_raster(raster):arcpy…

Windows下CMake通過鴻蒙SDK交叉編譯三方庫

前言 華為鴻蒙官方的文章CMake構建工程配置HarmonyOS編譯工具鏈 中介紹了在Linux平臺下如何使用CMake來配置鴻蒙的交叉編譯環境&#xff0c;編譯輸出在Harmony中使用的第三方so庫以及測試demo。 本文主要是在Windows下實現同樣的操作。由于平臺差異的原因&#xff0c;有些細節…

從C學C++(6)——構造函數和析構函數

從C學C(6)——構造函數和析構函數 若無特殊說明&#xff0c;本博客所執行的C標準均為C11. 構造函數與析構函數 構造函數定義 構造函數是特殊的成員函數&#xff0c;當創建類類型的新對象&#xff0c;系統自動會調用構造函數構造函數是為了保證對象的每個數據成員都被正確初…

清理 Windows C 盤該注意什么

C 盤空間不足會嚴重影響系統性能。 清理 C 盤文件時&#xff0c;首要原則是安全。錯誤地刪除系統文件會導致 Windows 無法啟動。下面我將按照 從最安全、最推薦到需要謹慎操作的順序&#xff0c;為你詳細列出可以清理的文件和文件夾&#xff0c;并提供操作方法。 第一梯隊&…

Python Selenium 滾動到特定元素

文章目錄 Python Selenium 滾動到特定元素?? **1. 使用 scrollIntoView() 方法&#xff08;最推薦&#xff09;**&#x1f5b1;? **2. 結合 ActionChains 移動鼠標&#xff08;模擬用戶行為&#xff09;**&#x1f9e9; **3. 使用坐標計算滾動&#xff08;精確控制像素&…

你寫的 Express 接口 404,可能是被“動態路由”吃掉了

本文首發在我的個人博客&#xff1a;你寫的 Express 接口 404&#xff0c;可能是被“動態路由”吃掉了 前情提要 最近參與公司的一個項目前端 React&#xff0c;后端用的 Express。目前我就做一些功能的新增或者修改。 對于 Express &#xff0c;本人沒有公司項目實戰經驗&…

【Java面試】你是怎么控制緩存的更新?

&#x1f504; 一、數據實時同步失效&#xff08;強一致性&#xff09; 原理&#xff1a;數據庫變更后立即失效或更新緩存&#xff0c;保證數據強一致。 實現方式&#xff1a; Cache Aside&#xff08;旁路緩存&#xff09;&#xff1a; 讀流程&#xff1a;讀緩存 → 未命中則…

react-嵌套路由 二級路由

什么是嵌套路由&#xff1f; 在一級路由中又內嵌了其他路由&#xff0c;這種關系就叫做嵌套路由&#xff0c;嵌套至一級路由內的路由又稱作二級路由 嵌套路由配置 實現步驟 配置二級路由 children嵌套 import Login from "../page/Login/index"; import Home from …

【CMake基礎入門教程】第八課:構建并導出可復用的 CMake 庫(支持 find_package() 查找)

很好&#xff01;我們進入 第八課&#xff1a;構建并導出可復用的 CMake 庫&#xff08;支持 find_package() 查找&#xff09;。 &#x1f3af; 本課目標 你將掌握&#xff1a; 如何構建一個庫并通過 install() 導出其配置&#xff1b; 如何讓別人在項目中使用 find_package…

Jenkins與Kubernetes深度整合實踐

采用的非jenkins-slave方式 jenkins配置&#xff1a; Jenkins添加k8s master節點的服務器信息 在Jenkins容器內部與k8s master節點設置免費登錄 # docker過濾查詢出運行的Jenkins服務 $ docker ps | grep jenkins# 進入Jenkins容器內部 $ docker exec -it jenkins-server /bi…

GraphQL API-1

簡介 判斷GraphQL方式 判斷一個網站是否使用了GraphQL API&#xff0c;可以通過以下幾種方法&#xff1a; 1. 檢查網絡請求 查看請求端點 GraphQL 通常使用單一端點&#xff0c;常見路徑如&#xff1a; /graphql/api/graphql/gql/query 觀察請求特征 POST 請求為主&…