觀察者模式(行為模式)

觀察者模式

觀察者模式屬于行為模式,個人理解:和發布訂閱者魔模式是有區別的
細分有兩種:推模式和拉模式兩種,具體區別在于推模式會自帶推送參數,拉模式是在接收通知后要自己獲取更新參數

觀察者模式(Observer Pattern)的使用場景主要圍繞對象間一對多的依賴關系,當一個對象(被觀察者)的狀態變化需要自動通知其他多個對象(觀察者)時,該模式能有效解耦代碼。以下是典型的使用場景和案例:


應用場景

1. GUI 事件處理

場景:用戶界面組件(如按鈕、輸入框)的狀態變化需要觸發多個事件監聽器。
案例

  • 點擊按鈕后,觸發日志記錄、界面更新、數據提交等多個操作。
  • 輸入框內容變化時,實時校驗輸入合法性并更新提示信息。
    框架應用:Java Swing、Android 的 OnClickListener、JavaScript 的 addEventListener

2. 實時數據同步

場景:數據源的變更需要實時同步到多個客戶端或組件。
案例

  • 股票行情系統:股價變動時,所有關注的投資者界面自動刷新。

  • 在線協作工具(如 Google Docs):一個用戶編輯內容,其他用戶的視圖實時更新。

  • 前端框架(如 Vue、React)的數據綁定:數據變化驅動視圖渲染。

  • 一個主界面由好幾個子界面垂直布局組成, 數據源的變更,子界面的數據將實時變化(頁面由還幾個一級標題頁面,為了解耦和代碼管理按照標題差分類結構)


3. 狀態監控與報警

場景:監控系統狀態變化,并觸發相關響應(如日志、報警、資源調整)。
案例

  • 服務器 CPU 使用率超過閾值時,觸發郵件報警、記錄日志、自動擴容。
  • 物聯網設備(如傳感器)數據異常時,通知用戶和管理系統。

4. 游戲開發中的事件系統

場景:游戲內事件(如角色死亡、任務完成)需要觸發多模塊響應。
案例

  • 玩家生命值降為 0 時,觸發 UI 更新死亡動畫、保存進度、播放音效。
  • 成就系統:當玩家達成特定條件(如擊殺 100 個敵人),解鎖成就并推送通知。

5. 配置或參數動態更新

場景:系統配置變更后,相關組件需動態調整行為,無需重啟。
案例

  • 修改系統主題顏色,所有界面組件自動切換配色。
  • 動態調整日志級別,實時生效。

6. 分布式系統中的一致性保證

場景:多個服務需要根據核心服務狀態變化保持一致性。
案例

  • 電商系統中,訂單狀態變為“已支付”時,通知庫存服務扣減庫存、物流服務生成運單。
  • 分布式緩存失效:當緩存數據更新,通知所有節點清除舊緩存。

觀察者模式的優勢

  • 解耦:被觀察者與觀察者之間松耦合,可獨立擴展。
  • 靈活性:動態添加/移除觀察者,符合開閉原則。
  • 一致性:確保所有依賴對象在狀態變化時同步更新。

注意事項

  • 性能問題:觀察者過多或通知邏輯復雜時,可能影響性能。
  • 循環依賴:避免觀察者間相互觸發導致死循環。
  • 內存泄漏:某些語言(如 Java)需手動注銷觀察者,防止對象無法回收。

何時選擇觀察者模式?

  • 一個對象的變化需要通知其他對象,且具體通知對象未知或可變。
  • 需要減少對象間的直接依賴,提升代碼復用性和可維護性。
  • 跨層級或跨模塊通信,尤其是事件驅動的系統架構。

推模式代碼

#include <iostream>
#include <vector>
#include <memory>// 觀察者接口
class Observer {
public:virtual void update(float temperature, float humidity) = 0; // 推送具體數據virtual ~Observer() = default;
};// 被觀察者(氣象站)
class WeatherStation {
private:std::vector<Observer*> observers;float temperature;float humidity;public:void addObserver(Observer* observer) {observers.push_back(observer);}void removeObserver(Observer* observer) {// 實際代碼中需要更安全的刪除邏輯observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end());}void setMeasurements(float temp, float hum) {temperature = temp;humidity = hum;notifyObservers();}private:void notifyObservers() {for (auto observer : observers) {observer->update(temperature, humidity); // 推送數據}}
};// 具體觀察者(顯示屏)
class Display : public Observer {
public:void update(float temperature, float humidity) override {std::cout << "顯示屏更新 - 溫度: " << temperature << "°C, 濕度: " << humidity << "%\n";}
};int main() {WeatherStation station;Display display;station.addObserver(&display);station.setMeasurements(25.5f, 60.0f); // 數據變化時自動推送return 0;
}

拉模式代碼

#include <iostream>
#include <vector>
#include <memory>// 觀察者接口
class Observer {
public:virtual void update() = 0; // 不推送數據,觀察者自行拉取virtual ~Observer() = default;
};// 被觀察者(氣象站)
class WeatherStation {
private:std::vector<Observer*> observers;float temperature;float humidity;public:void addObserver(Observer* observer) {observers.push_back(observer);}void removeObserver(Observer* observer) {observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end());}void setMeasurements(float temp, float hum) {temperature = temp;humidity = hum;notifyObservers();}// 觀察者通過接口拉取數據float getTemperature() const { return temperature; }float getHumidity() const { return humidity; }private:void notifyObservers() {for (auto observer : observers) {observer->update(); // 僅通知,不傳遞數據}}
};// 具體觀察者(顯示屏)
class Display : public Observer {
private:WeatherStation& station; // 觀察者持有被觀察者的引用以拉取數據public:Display(WeatherStation& station) : station(station) {}void update() override {float temp = station.getTemperature();float hum = station.getHumidity();std::cout << "顯示屏更新 - 溫度: " << temp << "°C, 濕度: " << hum << "%\n";}
};int main() {WeatherStation station;Display display(station); // 觀察者需要持有被觀察者的引用station.addObserver(&display);station.setMeasurements(25.5f, 60.0f); // 數據變化時通知觀察者return 0;
}

和發布訂閱者模式的區別:

觀察者模式(Observer Pattern)和發布-訂閱模式(Pub-Sub Pattern)是兩種常用于解耦對象間通信的設計模式,但它們的設計思想和應用場景有顯著區別。以下是兩者的核心差異和對比:


1. 核心機制與角色關系

特性觀察者模式發布-訂閱模式
通信方式直接通信:被觀察者(Subject)直接通知觀察者(Observer)。間接通信:發布者(Publisher)和訂閱者(Subscriber)通過**中介層(Broker/Event Bus)**交互。
角色關系- 被觀察者(Subject)
- 觀察者(Observer)
- 發布者(Publisher)
- 訂閱者(Subscriber)
- 中介層(Broker)
耦合度較高:觀察者需要直接注冊到被觀察者,依賴其接口。極低:發布者和訂閱者彼此無感知,僅依賴中介層和事件類型。
事件路由被觀察者自行管理觀察者列表,并決定通知邏輯。中介層負責事件的路由、過濾和廣播(例如按主題、標簽或內容匹配)。

2. 典型代碼結構對比

(1) 觀察者模式
// 被觀察者(Subject)
class WeatherStation {
private:std::vector<Observer*> observers;
public:void addObserver(Observer* observer) { /*注冊觀察者*/ }void notifyObservers() {for (auto obs : observers) {obs->update(temperature); // 直接調用觀察者的接口}}
};// 觀察者(Observer)
class Display : public Observer {
public:void update(float temp) override { /*更新顯示*/ }
};
(2) 發布-訂閱模式
// 中介層(Broker)
class MessageBroker {
private:std::unordered_map<std::string, std::vector<Subscriber*>> topicSubscribers;
public:void subscribe(const std::string& topic, Subscriber* sub) { /*按主題訂閱*/ }void publish(const std::string& topic, const std::string& message) {for (auto sub : topicSubscribers[topic]) {sub->onMessage(message); // 通過中介層轉發消息}}
};// 訂閱者(Subscriber)
class User : public Subscriber {
public:void onMessage(const std::string& msg) override { /*處理消息*/ }
};

3. 關鍵區別

維度觀察者模式發布-訂閱模式
通信方向單向:被觀察者 → 觀察者多向:發布者 → 中介層 → 訂閱者(支持多對多通信)
動態性觀察者需顯式注冊到具體被觀察者訂閱者通過中介層動態訂閱事件類型(如主題、頻道)
擴展性新增事件類型需修改被觀察者接口新增事件類型只需在中介層注冊,無需修改發布者或訂閱者
適用場景對象間一對多的簡單依賴關系(如GUI事件、狀態同步)復雜的多對多通信、跨系統解耦(如微服務、消息隊列)
典型應用- 按鈕點擊事件監聽
- 數據模型更新UI
- 新聞訂閱系統
- 分布式系統的異步通信
- 實時聊天室

4. 場景示例

(1) 觀察者模式適用場景
  • GUI事件處理
    按鈕(被觀察者)被點擊時,直接通知所有注冊的監聽器(觀察者)執行操作。

    button.addClickListener(&logListener);  // 日志監聽器
    button.addClickListener(&uiUpdater);    // 界面更新監聽器
    
  • 游戲狀態同步
    角色血量變化時,通知UI組件、音效模塊、成就系統更新。

(2) 發布-訂閱模式適用場景
  • 新聞訂閱系統
    用戶(訂閱者)訂閱“科技”主題,發布者發布新聞時,中介層將消息推送給所有訂閱者。

    broker.subscribe("科技", &user1);  // 用戶訂閱主題
    broker.publish("科技", "AI新突破!");  // 發布者發送消息
    
  • 微服務通信
    訂單服務(發布者)發布“訂單創建”事件,庫存服務(訂閱者)接收事件并扣減庫存。


5. 總結:何時選擇哪種模式?

模式選擇條件
觀察者模式- 對象間關系簡單(一對多)
- 需要直接控制通知邏輯
- 實時性要求高
發布-訂閱模式- 系統需要解耦(多對多)
- 動態事件類型和訂閱關系
- 跨組件或跨系統通信

6. 常見誤區

  • 誤區1:發布-訂閱是觀察者模式的“升級版”。
    糾正:兩者解決不同問題。觀察者模式強調直接通信,發布-訂閱強調間接通信和解耦。

  • 誤區2:中介層(如 TalkNotifier)的存在即表示發布-訂閱模式。
    糾正:觀察者模式中的 Subject 也可以視為簡單中介,但發布-訂閱的中介層更獨立且支持復雜路由。

  • 誤區3:發布-訂閱必須異步。
    糾正:發布-訂閱可以實現為同步或異步,而觀察者模式通常是同步的。


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

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

相關文章

內網滲透 --- 之殺軟工具探測

目錄 內網殺軟探測與應對實戰方案 一、總體思路 二、探測階段——殺軟工具與手法 2.1 進程與服務檢測 2.2 注冊表與文件系統檢測 2.3 Nmap 與 NSE 腳本掃描 三、處理階段——探測到殺軟后的應對措施 3.1 分析評估 3.2 應對策略 四、判斷與驗證——注入 webshell 后如…

(2025親測可用)Chatbox多端一鍵配置Claude/GPT/DeepSeek-網頁端配置

1. 資源準備 API Key&#xff1a;此項配置填寫在一步API官網創建API令牌&#xff0c;一鍵直達API令牌創建頁面創建API令牌步驟請參考API Key的獲取和使用API Host&#xff1a;此項配置填寫https://yibuapi.com/v1查看支持的模型請參考這篇教程模型在線查詢 2. ChatBox網頁版配…

【Pandas】pandas DataFrame keys

Pandas2.2 DataFrame Indexing, iteration 方法描述DataFrame.head([n])用于返回 DataFrame 的前幾行DataFrame.at快速訪問和修改 DataFrame 中單個值的方法DataFrame.iat快速訪問和修改 DataFrame 中單個值的方法DataFrame.loc用于基于標簽&#xff08;行標簽和列標簽&#…

Redis存儲“大數據對象”的常用策略及StackOverflowError錯誤解決方案

Hi&#xff0c;大家好&#xff0c;我是灰小猿&#xff01; 在一些功能的開發中&#xff0c;我們一般會有一些場景需要將得到的數據先暫時的存儲起來&#xff0c;以便后面的接口或業務使用&#xff0c;這種場景我們一般常用的場景就是將數據暫時存儲在緩存中&#xff0c;之后再…

【Python】讀取xyz坐標文件輸出csv文件

Python讀取xyz坐標文件輸出csv文件 import sys import numpy as np import pandas as pd from tqdm import tqdm import cv2 import argparsedef read_xyz(file_path):with open(file_path, "r") as f: # 打開文件data f.readlines() # 讀取文件datas []for …

leetcode 139. Word Break

這道題用動態規劃解決。 class Solution { public:bool wordBreak(string s, vector<string>& wordDict) {unordered_set<string> wordSet;for(string& word:wordDict){wordSet.insert(word);}int s_len s.size();//s的下標從1開始起算&#xff0c;dp[j]…

驅動開發硬核特訓 · Day 11(下篇):從 virtio_blk 看虛擬總線驅動模型的真實落地

&#x1f50d; B站相應的視屏教程&#xff1a; &#x1f4cc; 內核&#xff1a;博文視頻 - 總線驅動模型實戰全解析 敬請關注&#xff0c;記得標為原始粉絲。 &#x1f527; 在上篇中&#xff0c;我們已經從理論視角分析了“虛擬總線驅動模型”在 Linux 驅動體系中的獨特定位。…

音視頻轉換器 AV 接口靜電保護方案

方案簡介 音視頻轉換器是將音視頻&#xff08;AV&#xff09;信號轉換成其他格式或信號類型的設備或軟件。 它能夠實現大多數視頻、音頻以及圖像格式之間的轉換&#xff0c;包括但不限于 RMVB、AVI、 MP4、MOV 等常見格式&#xff0c;同時也支持將不同采樣率、位深度、聲道數…

AI agents系列之全從零開始構建

在我們上一篇博客文章中&#xff0c;我們全面介紹了智能代理&#xff0c;討論了它們的特性、組成部分、演變過程、面臨的挑戰以及未來的可能性。 這篇文章&#xff0c;咱們就來聊聊怎么用 Python 從零開始構建一個智能代理。這個智能代理能夠根據用戶輸入做出決策&#xff0c;…

【Python爬蟲】詳細工作流程以及組成部分

目錄 一、Python爬蟲的詳細工作流程 確定起始網頁 發送 HTTP 請求 解析 HTML 處理數據 跟蹤鏈接 遞歸抓取 存儲數據 二、Python爬蟲的組成部分 請求模塊 解析模塊 數據處理模塊 存儲模塊 調度模塊 反爬蟲處理模塊 一、Python爬蟲的詳細工作流程 在進行網絡爬蟲工…

Kotlin 集合過濾全指南:all、any、filter 及高級用法

在 Kotlin 中&#xff0c;集合過濾是數據處理的核心操作之一。無論是簡單的條件篩選&#xff0c;還是復雜的多條件組合&#xff0c;Kotlin 都提供了豐富的 API。本文將詳細介紹 filter、all、any、none 等操作符的用法&#xff0c;并展示如何在實際開發中靈活運用它們。 1. 基礎…

爬蟲:一文掌握 curl-cffi 的詳細使用(支持 TLS/JA3 指紋仿真的 cURL 庫)

更多內容請見: 爬蟲和逆向教程-專欄介紹和目錄 文章目錄 一、curl-cffi 概述1.1 curl-cffi介紹1.2 主要特性1.3 適用場景1.4 使用 curl-cffi 的注意事項1.5 與 requests 和 pycurl 對比1.6 curl-cffi 的安裝二、基本使用2.1 同步請求2.2 異步請求三、高級功能3.1 模擬瀏覽器指…

AllData數據中臺升級發布 | 支持K8S數據平臺2.0版本

&#x1f525;&#x1f525; AllData大數據產品是可定義數據中臺&#xff0c;以數據平臺為底座&#xff0c;以數據中臺為橋梁&#xff0c;以機器學習平臺為中層框架&#xff0c;以大模型應用為上游產品&#xff0c;提供全鏈路數字化解決方案。 ?杭州奧零數據科技官網&#xf…

dnf install openssl失敗的原因和解決辦法

網上有很多編譯OpenSSL源碼(3.x版本)為RPM包的文章&#xff0c;這些文章在安裝RPM包時都是執行rpm -ivh openssl-xxx.rpm --nodeps --force 這個命令能在缺少依賴包的情況下能強行執行安裝 其實根據Centos的文檔&#xff0c;安裝RPM包一般是執行yum install或dnf install。后者…

從入門到進階:React 圖片輪播 Carousel 的奇妙世界!

全文目錄&#xff1a; 開篇語&#x1f590; 前言? 目錄&#x1f3af; 什么是圖片輪播組件&#xff1f;&#x1f528; 初識 React 中的輪播實現示例代碼分析 &#x1f4e6; 基于第三方庫快速實現輪播示例&#xff1a;用 react-slick優勢局限性 &#x1f6e0;? 自己動手實現一個…

2025第十六屆藍橋杯PythonB組部分題解

一、攻擊次數 題目描述 小藍操控三個英雄攻擊敵人&#xff0c;敵人初始血量2025&#xff1a; 第一個英雄每回合固定攻擊5點第二個英雄奇數回合攻擊15點&#xff0c;偶數回合攻擊2點第三個英雄根據回合數除以3的余數攻擊&#xff1a;余1攻2點&#xff0c;余2攻10點&#xff0…

新手寶塔部署thinkphp一步到位

目錄 一、下載對應配置 二、加載數據庫 三、添加FTP? 四、上傳項目到寶塔? 五、添加站點? 六、配置偽靜態 七、其他配置 開啟監控 八、常見錯誤 一、打開寶塔頁面&#xff0c;下載對應配置。 二、加載數據庫 從本地導入數據庫文件 三、添加FTP 四、上傳項目到寶塔…

2025年,HarmonyOS認證學習及考試

HarmonyOS應用開發者認證考試 基礎認證 通過系統化的課程學習&#xff0c;熟練掌握 DevEco Studio&#xff0c;ArkTS&#xff0c;ArkUI&#xff0c;預覽器&#xff0c;模擬器&#xff0c;SDK 等 HarmonyOS 應用開發的關鍵概念&#xff0c;具備基礎的應用開發能力。 高級認證…

3-1 Git分布式版本控制特性探討

Git 的分布式版本控制特性是其核心優勢之一,它使 Git 在版本管理方面具有高度的靈活性、可靠性和高效性。以下從多個方面來理解這一特性: 分布式存儲 在 Git 中,每個開發者的本地機器上都擁有完整的版本庫,包含了項目的所有歷史記錄和元數據。這與集中式版本控制系統(如…

flutter 桌面應用之右鍵菜單

?在 Flutter 桌面應用開發中&#xff0c;context_menu 和 contextual_menu 是兩款常用的右鍵菜單插件&#xff0c;各有特色。以下是對它們的對比分析&#xff1a;? context_menu 集成方式&#xff1a;?通過 ContextMenuArea 組件包裹目標組件&#xff0c;定義菜單項。?掘金…