觀察者模式 (Observer Pattern)與幾個C++應用例子

1. 模式定義與核心思想
觀察者模式定義了一種一對多的依賴關系,讓多個觀察者對象同時監聽某一個主題對象。當這個主題對象的狀態發生變化時,它會自動通知所有觀察者對象,使它們能夠自動更新自己。

核心思想: 解耦主題和觀察者。主題不需要知道哪些具體對象在觀察它,它只需要維護一個觀察者列表,并在狀態改變時向它們發送通知。這使得系統更靈活,可以動態地添加和刪除觀察者,而無需修改主題的代碼。

2. 模式結構(角色分析)
觀察者模式通常包含以下四個角色:

Subject (主題 / 被觀察者):

維護一個觀察者(Observer)對象的集合。

提供接口可以添加(Attach)和刪除(Detach)觀察者。

提供接口用于通知(Notify)所有注冊的觀察者。

ConcreteSubject (具體主題):

繼承自 Subject。

維護其自身的狀態(例如,一個溫度值、一個鼠標點擊位置)。

當狀態發生改變時,調用父類的 Notify 方法通知所有觀察者。

Observer (觀察者):

為所有具體觀察者定義一個更新接口。通常是一個抽象的 Update() 方法。

當接到主題的通知時,調用此方法做出響應。

ConcreteObserver (具體觀察者):

繼承自 Observer。

實現 Update() 方法。在此方法中,通常會從 ConcreteSubject 獲取所需的狀態,并執行具體的業務邏輯(如更新UI、記錄日志等)。

通常會維護一個指向 ConcreteSubject 的引用,以便在更新時獲取其狀態。

它們之間的協作關系如下圖所示:
(這是一個UML協作序列的文本描述)

ConcreteObserver 調用 ConcreteSubject 的 Attach 方法將自己注冊到主題的觀察者列表中。

ConcreteSubject 的內部狀態發生變化。

ConcreteSubject 調用 Notify 方法。

Notify 方法遍歷所有注冊的 Observer,并調用每個觀察者的 Update 方法。

ConcreteObserver 的 Update 方法被調用,它可以通過傳入的參數或查詢 ConcreteSubject 來獲取新狀態,并據此更新自身。

3. 經典C++實現示例
下面是一個簡單的C++實現,模擬一個氣象站(主題)和多個顯示設備(觀察者)。

#include <iostream>
#include <vector>
#include <string>
#include <memory>// 前向聲明
class Subject;// 1. Observer (觀察者接口)
class Observer {
public:virtual ~Observer() = default;// 更新接口,參數通常是Subject或狀態數據virtual void Update(Subject& subject) = 0;
};// 2. Subject (主題接口)
class Subject {
public:virtual ~Subject() = default;// 注冊觀察者void Attach(std::shared_ptr<Observer> observer) {observers_.push_back(observer);}// 移除觀察者void Detach(std::shared_ptr<Observer> observer) {// 在實際項目中,這里需要更安全的刪除邏輯observers_.erase(std::remove(observers_.begin(), observers_.end(), observer),observers_.end());}// 通知所有觀察者void Notify() {for (auto& observer : observers_) {observer->Update(*this);}}private:std::vector<std::shared_ptr<Observer>> observers_;
};// 3. ConcreteSubject (具體主題:氣象站)
class WeatherStation : public Subject {
public:// 設置狀態(溫度)并通知觀察者void SetTemperature(double temp) {temperature_ = temp;Notify(); // 關鍵一步:狀態改變,立即通知所有觀察者}// 獲取狀態(供觀察者查詢)double GetTemperature() const {return temperature_;}private:double temperature_ = 0.0;
};// 4. ConcreteObserver (具體觀察者:手機顯示)
class PhoneDisplay : public Observer {
public:explicit PhoneDisplay(const std::string& name) : name_(name) {}void Update(Subject& subject) override {// 安全的向下轉型,確認主題類型WeatherStation* ws = dynamic_cast<WeatherStation*>(&subject);if (ws) {double temp = ws->GetTemperature();std::cout << "[" << name_ << "] Temperature updated: " << temp << "°C\n";}}private:std::string name_;
};// 5. 另一個具體觀察者:LED大屏顯示
class LedDisplay : public Observer {
public:void Update(Subject& subject) override {WeatherStation* ws = dynamic_cast<WeatherStation*>(&subject);if (ws) {double temp = ws->GetTemperature();std::cout << "*** LED Display: CURRENT TEMP = " << temp << "°C ***\n";}}
};// 客戶端代碼
int main() {// 創建主題(氣象站)WeatherStation station;// 創建觀察者(兩個顯示設備)auto phone1 = std::make_shared<PhoneDisplay>("User's Phone");auto phone2 = std::make_shared<PhoneDisplay>("Dad's Phone");auto led = std::make_shared<LedDisplay>();// 注冊觀察者station.Attach(phone1);station.Attach(phone2);station.Attach(led);// 模擬氣象站溫度變化,觀察者會自動更新std::cout << "Setting temperature to 25.5°C...\n";station.SetTemperature(25.5);std::cout << "\nSetting temperature to 18.2°C...\n";station.SetTemperature(18.2);// 移除一個觀察者std::cout << "\nDetaching Dad's Phone...\n";station.Detach(phone2);std::cout << "Setting temperature to 30.0°C...\n";station.SetTemperature(30.0);return 0;
}

輸出結果:

Setting temperature to 25.5°C...
[User's Phone] Temperature updated: 25.5°C
[Dad's Phone] Temperature updated: 25.5°C
*** LED Display: CURRENT TEMP = 25.5°C ***Setting temperature to 18.2°C...
[User's Phone] Temperature updated: 18.2°C
[Dad's Phone] Temperature updated: 18.2°C
*** LED Display: CURRENT TEMP = 18.2°C ***Detaching Dad's Phone...
Setting temperature to 30.0°C...
[User's Phone] Temperature updated: 30°C
*** LED Display: CURRENT TEMP = 30°C ***

更多例子:

示例 1: GUI 事件處理 (按鈕點擊)

#include <iostream>
#include <vector>
#include <memory>
#include <algorithm>// 前向聲明
class Button;// 觀察者接口:事件監聽器
class EventListener {
public:virtual ~EventListener() = default;virtual void onClick(Button& source) = 0;
};// 主題:按鈕
class Button {std::vector<EventListener*> listeners_; // 使用原始指針簡化示例std::string name_;public:Button(const std::string& name) : name_(name) {}// 注冊觀察者void addListener(EventListener* listener) {listeners_.push_back(listener);}// 移除觀察者void removeListener(EventListener* listener) {listeners_.erase(std::remove(listeners_.begin(), listeners_.end(), listener), listeners_.end());}// 模擬用戶點擊void click() {std::cout << "Button '" << name_ << "' was clicked!\n";notifyListeners();}const std::string& getName() const { return name_; }private:// 通知所有觀察者void notifyListeners() {for (auto listener : listeners_) {listener->onClick(*this);}}
};// 具體觀察者:登錄處理器
class LoginHandler : public EventListener {
public:void onClick(Button& source) override {std::cout << "[LoginHandler] Handling click from: " << source.getName() << "\n";std::cout << "Performing login logic...\n";}
};// 具體觀察者:日志記錄器
class ClickLogger : public EventListener {
public:void onClick(Button& source) override {std::cout << "[ClickLogger] Button clicked: " << source.getName() << " at timestamp: 12345\n";}
};int main() {Button loginButton("Login");LoginHandler loginHandler;ClickLogger logger;// 注冊監聽器loginButton.addListener(&loginHandler);loginButton.addListener(&logger);// 模擬點擊事件loginButton.click();std::cout << "\nRemoving logger...\n";loginButton.removeListener(&logger);loginButton.click();return 0;
}

輸出結果:

Button 'Login' was clicked!
[LoginHandler] Handling click from: Login
Performing login logic...
[ClickLogger] Button clicked: Login at timestamp: 12345Removing logger...
Button 'Login' was clicked!
[LoginHandler] Handling click from: Login
Performing login logic...

示例 2: 發布-訂閱系統 (簡單的消息主題)

#include <iostream>
#include <vector>
#include <string>
#include <map>
#include <algorithm>// 消息類
struct Message {std::string topic;std::string content;
};// 訂閱者接口
class Subscriber {
public:virtual ~Subscriber() = default;virtual void onMessage(const Message& msg) = 0;
};// 消息代理(主題)
class MessageBroker {std::map<std::string, std::vector<Subscriber*>> topicSubscribers_;public:// 訂閱主題void subscribe(const std::string& topic, Subscriber* sub) {topicSubscribers_[topic].push_back(sub);}// 取消訂閱void unsubscribe(const std::string& topic, Subscriber* sub) {auto& subs = topicSubscribers_[topic];subs.erase(std::remove(subs.begin(), subs.end(), sub), subs.end());}// 發布消息void publish(const Message& msg) {auto it = topicSubscribers_.find(msg.topic);if (it != topicSubscribers_.end()) {for (auto sub : it->second) {sub->onMessage(msg);}}}
};// 具體訂閱者:日志服務
class LogService : public Subscriber {
public:void onMessage(const Message& msg) override {std::cout << "[LogService] Received on topic '" << msg.topic << "': " << msg.content << "\n";}
};// 具體訂閱者:告警服務
class AlertService : public Subscriber {
public:void onMessage(const Message& msg) override {if (msg.topic == "alerts") {std::cout << "[AlertService] ALERT! " << msg.content << "\n";}}
};int main() {MessageBroker broker;LogService logger;AlertService alerter;// 訂閱主題broker.subscribe("logs", &logger);broker.subscribe("alerts", &logger);broker.subscribe("alerts", &alerter);// 發布消息broker.publish({"logs", "System started successfully"});broker.publish({"alerts", "CPU usage over 90%"});broker.publish({"metrics", "This will be ignored by all"}); // 無訂閱者return 0;
}

輸出結果:

[LogService] Received on topic 'logs': System started successfully
[LogService] Received on topic 'alerts': CPU usage over 90%
[AlertService] ALERT! CPU usage over 90%

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

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

相關文章

[系統架構設計師]論文(二十三)

[系統架構設計師]論文&#xff08;二十三&#xff09; 一.論軟件系統架構評估 1.架構所關注的質量屬性主要有&#xff1a;性能&#xff0c;可用性&#xff0c;安全性&#xff0c;可修改性 1&#xff09;性能。性能是指系統的響應能力&#xff0c;即要經過多長時間才能對某個事件…

攻克 Java 分布式難題:并發模型優化與分布式事務處理實戰指南

攻克 Java 分布式難題&#xff1a;并發模型優化與分布式事務處理實戰指南 開場&#xff1a;從“搖搖欲墜”到“穩如磐石”&#xff0c;你的分布式系統進階之路 你是否曾經遇到過這樣的場景&#xff1f;精心打造的電商應用&#xff0c;在大促開啟的瞬間&#xff0c;頁面響應變得…

如何在Ubuntu中刪除或修改已有的IP地址設置?

在 Ubuntu 中為新增加的網卡設置網絡時&#xff0c;需要區分原有網卡和新網卡的配置&#xff0c;確保它們可以獨立工作&#xff08;可在同一網段或不同網段&#xff09;。以下是具體步驟&#xff0c;假設你需要為新網卡配置靜態 IP&#xff08;以 192.168.1.190/24 為例&#x…

Ansible Playbook 概述與實踐案例(下)

#作者&#xff1a;張桐瑞 文章目錄四、條件判斷的實現五、循環的實現六、Jinja模板應用1、Jinja模板2、handlers組件七、角色 role1、角色介紹2、案例: 部署zabbix-agent四、條件判斷的實現 when: 條件 - hosts: appserveruser: roottasks:- name: create userAuser: nameuser…

LeetCode 100 -- Day6

1. 哈希&#xff1a;49、128&#xff08;1&#xff09;49 字母異位詞分組 -- 字典from collections import defaultdict class Solution(object):def groupAnagrams(self, strs):"""創建字典{sorted_string&#xff1a;原str}"""resultsdefaultd…

多因素認證(MFA/2FA)實戰指南:如何保護你的賬號

一、MFA/2FA 基礎認知 1. 概念辨析與演進 單因素認證&#xff08;1FA&#xff09;的局限性&#xff1a;僅依賴 “知識因素”&#xff08;如密碼&#xff09;&#xff0c;據 2024 年 Verizon 數據泄露報告&#xff0c;81% 的賬戶入侵源于密碼泄露 —— 要么是用戶使用弱密碼&a…

vue3 字符 居中顯示

在Vue 3中&#xff0c;要實現字符的居中顯示&#xff0c;你可以使用多種方法&#xff0c;具體取決于你是想在HTML元素內居中文本&#xff0c;還是在CSS樣式中實現。下面是一些常見的方法&#xff1a;1. 使用內聯樣式你可以直接在元素上使用style屬性來實現文本的居中。<temp…

《Spring Boot 進階:從零到一打造自定義 @Transactional》 ——支持多數據源、動態傳播行為、可插拔回滾策略

《Spring Boot 進階&#xff1a;從零到一打造自定義 Transactional》 ——支持多數據源、動態傳播行為、可插拔回滾策略版本&#xff1a;Spring Boot 3.2.x JDK 17一、背景與痛點痛點默認 Transactional 限制多數據源只能綁定一個 DataSourceTransactionManager多租戶無法在運…

open3D學習筆記

這里寫自定義目錄標題 核心3D數據結構 1.1 PointCloud(點云) 最近鄰搜索 (KNN/Radius) 與空間索引(KDTree/Octree) 法線估計 (Normal Estimation) 聚類分割 (基于歐氏距離的聚類) 1.2 TriangleMesh (三角形網格) 泊松表面重建 (Poisson Surface Reconstruction) 滾球法 (Ba…

gt_k_char設計模塊

是不是再fiber或者gt設計中經常遇到接收數據沒有對齊&#xff1f;是的。很多協議需要手動對齊設計。這不&#xff0c;它來了。下面是手動對齊代碼設計&#xff0c;本人在很多工程和項目中應用過&#xff0c;現在共享出來&#xff0c;給大家使用。module gt_k_char (input …

網頁版云手機怎么樣

隨著科技的不斷發展&#xff0c;云手機這一新興概念逐漸走入大眾視野&#xff0c;而網頁版云手機作為云手機的一種便捷使用方式&#xff0c;備受關注&#xff0c;下面從多個方面來探討網頁版云手機究竟怎么樣。與傳統的需要在本地設備安裝專門APP的云手機使用方式不同&#xff…

XFile v2 系統架構文檔

XFile v2 系統架構文檔 1. 概述 XFile 是一個基于 Go 語言開發的分布式文件管理系統&#xff0c;提供本地文件存儲、網絡文件共享、安全認證和多種文件操作功能。該系統采用模塊化設計&#xff0c;支持大文件分片存儲、用戶權限管理、雙因素認證等高級功能。 XFile系統的核心特…

寫一個天氣查詢Mcp Server

上篇文章&#xff0c;我們聊到了 MCP 的基本概念&#xff0c;帶大家快速入門了 MCP。 說入門應該毫不夸張&#xff0c;對于科普性質的文章&#xff0c;只需要知道這件事情的誕生背景以及有什么作用就可以了。 但是&#xff0c;如果要開發給大模型調用的 Mcp Server&#xff0…

leecode-三數之和

思路 我的思路先順序遍歷一個變量,然后使用首尾雙指針去遍歷&#xff0c;根據結果去更新另外兩個變量&#xff0c;如何和為零&#xff0c;將結果加入集合&#xff0c;但是這里要注意去重。 class Solution {public List<List<Integer>> threeSum(int[] nums) {// 排…

【數學建模】灰色關聯分析的核心步驟

文章目錄步驟一&#xff1a;讀數據步驟二&#xff1a;指標正向化步驟三&#xff1a;數據標準化步驟三&#xff1a;數據標準化步驟四&#xff1a;結果處理步驟一&#xff1a;讀數據 步驟一&#xff1a;讀數據 X xlsread(‘blind date.xlsx’); % 讀取Excel文件中的相親數據 詳…

基于高德地圖的懷化旅發精品路線智能規劃導航之旅

目錄 前言 一、2025湖南旅發 1、關于旅發 2、精品路線發布 二、高德技術賦能 1、地理編碼服務簡介 2、地理編碼服務參數介紹 3、自駕路徑規劃 4、自駕路徑規劃參數介紹 三、Java集成高德地圖服務 1、業務調用時序 2、Java地理編碼服務 3、Java路徑規劃 4、整體集成…

OpenCV實戰1.信用卡數字識別

1. 任務說明 有如下幾張信用卡&#xff0c;我們需要根據模板匹配出其中的數字&#xff0c;進行卡號的識別2. Debug源碼 cursor的debug&#xff1a;launch.json&#xff1a; {// 使用 IntelliSense 了解相關屬性。 // 懸停以查看現有屬性的描述。// 欲了解更多信息&#xff0c;請…

Spring Security 深度學習(一): 基礎入門與默認行為分析

目錄1. 引言&#xff1a;為何選擇Spring Security&#xff1f;2. 核心概念&#xff1a;認證 (Authentication) 與 授權 (Authorization)2.1 什么是認證 (Authentication)&#xff1f;2.2 什么是授權 (Authorization)&#xff1f;2.3 安全性上下文 (SecurityContext)3. Spring B…

數學建模--模糊綜合評價法

一、概念 模糊綜合評價法是一種基于模糊數學的綜合評價方法。它針對評價過程中存在的模糊性&#xff08;如 “好”“較好”“差” 等模糊概念&#xff09;&#xff0c;通過建立模糊集合&#xff0c;將定性評價轉化為定量評價&#xff0c;從而對具有多種屬性的評價對象做出全面、…

科普 | 5G支持的WWC架構是個啥(2)?

為解決有線固定寬帶與無線移動寬帶融合問題&#xff0c;3GPP在5G中推出了WWC系統架構。它將兩種接入類型統一融合到5G核心網絡。這有助于運營商簡化控制、簡化管理并為終端用戶提供一致服務&#xff1b;其中&#xff1a;一、5G核心組件包括&#xff1a;AMF(接入和移動性管理功能…