深入淺出設計模式——行為型模式之觀察者模式 Observer

文章目錄

  • 1.觀察者模式簡介
  • 2.觀察者模式結構
  • 3.觀察者模式代碼實例
    • 3.0.公共頭文件
    • 3.1.觀察者
      • 3.1.1.抽象觀察者Observer
      • 3.1.2.具體觀察者Player
    • 3.2.目標類
      • 3.2.1.抽象目標AllyCenter
      • 3.2.2.具體目標AllyCenterController
    • 循環包含
      • 錯誤示例
    • “前向聲明什么時候不夠、必須 #include 對方頭?”
    • 3.3.客戶端代碼示例及效果
  • 4.觀察者模式的應用
  • 5.總結

代碼倉庫

觀察者模式非常常見,近年來逐漸流行的響應式編程就是觀察者模式的應用之一。觀察者模式的思想就是一個對象發生一個事件后,逐一通知監聽著這個對象的監聽者,監聽者可以對這個事件馬上做出響應。

生活中有很多觀察者模式的例子,比如我們平時的開關燈。當我們打開燈的開關時,燈馬上亮了;當我們關閉燈的開關時,燈馬上熄了。這個過程中,燈就對我們控制開關的事件做出了響應,這就是一個最簡單的一對一觀察者模式。當某某公眾號發表一篇文章,所有關注了公眾號的讀者立即收到了文章,這個過程中所有關注了公眾號的微信客戶端就對公眾號發表文章的事件做出了響應,這就是一個典型的一對多觀察者模式。 這表明“更新發布文章”并不是孤立的,而是與眾多對象產生了關聯。一個對象行為的改變,其相關聯的對象都會得到通知,并自動產生對應的行為。這在軟件設計模式中,即是觀察者模式。

再舉個例子,比如警察一直觀察著張三的一舉一動,只要張三有什么違法行為,警察馬上行動,抓捕張三。

在這里插入圖片描述

眾所周知,張三壞事做盡,是一個老法外狂徒了,所以不止一個警察會盯著張三,也就是說一個被觀察者可以有多個觀察者。當被觀察者有事件發生時,所有觀察者都能收到通知并響應。觀察者模式主要處理的是一種一對多的依賴關系。 它的定義如下:

觀察者模式(Observer Pattern):定義對象間的一種一對多的依賴關系,當一個對象的狀態發生改變時,所有依賴于它的對象都得到通知并被自動更新。

1.觀察者模式簡介

軟件系統中的對象并不是孤立存在的,一個對象行為的改變可能會引起其他所關聯的對象的狀態或行為也發生改變,即“牽一發而動全身”。觀察者模式建立了一種一對多的聯動,一個對象改變時將自動通知其他對象,其他對象將作出反應。觀察者模式中,發生改變的對象稱為“觀察目標”,被通知的對象稱為“觀察者”。一個觀察目標可以有很多個觀察者。

觀察者模式定義如下:
觀察者模式:定義對象之間的一種一對多的依賴關系,使得每當一個對象狀態發生改變時,其相關依賴對象都得到通知并被自動更新。

觀察者模式又被稱為發布-訂閱模式(Publish-Subscribe)、模型-視圖模式(Model-View)、源-監聽器模式(Source-Listener)、從屬者模式(Dependents)。

2.觀察者模式結構

觀察者模式由觀察者和觀察目標組成,為便于擴展,兩個角色都設計了抽象層。觀察者模式的UML圖如下:
在這里插入圖片描述
在這里插入圖片描述
下述是觀察者模式的典型實現:

#define __DEMO_H__
#ifdef __DEMO_H__using namespace std;
#include <list>// 抽象觀察者
class Observer
{
public:virtual ~Observer() {}// 聲明響應更新方法virtual void update() = 0;
};// 具體觀察者
class ConcreteObserver : public Observer
{
public:// 實現響應更新方法void update(){// 具體操作}
};// 抽象目標
class Subject
{
public:virtual ~Subject() {}// 添加觀察者void attach(Observer *obs){obsList.push_back(obs);}// 移除觀察者void detach(Observer *obs){obsList.remove(obs);}// 聲明通知方法virtual void notify() = 0;protected:// 觀察者列表list<Observer *> obsList;
};// 具體目標
class ConcreteSubject : public Subject
{
public:// 實現通知方法void notify(){// 具體操作// 遍歷通知觀察者對象for (int i = 0; i < obsList.size(); i++){obsList[i]->update();}}
};// 客戶端代碼示例
int main()
{Subject *sub = new ConcreteSubject();Observer *obs = new ConcreteObserver();sub->attach(obs);sub->notify();delete sub;delete obs;return 0;
}
#endif

3.觀察者模式代碼實例

玩過和平精英這款游戲嗎?四人組隊絕地求生,當一個隊友發現物資時,可以發消息“我這里有物資”,其余三個隊友聽到后可以去取物資;當一個隊友遇到危險時,也可以發消息“救救我”,其余三個隊友得到消息后便立馬趕去營救。本例將用觀察者模式來模擬這個過程。

本例的UML圖如下:
在這里插入圖片描述
本例中,抽象觀察者是Observer,聲明了發現物資或者需要求救時的呼叫的方法call(),具體觀察者是Player,即玩家,Player實現了呼叫call()方法,并且還定義了取物資come()和支援隊友help()的方法本例定義了AllyCenter作為抽象目標,它維護了一個玩家列表playerList,并且定義了加入戰隊和剔除玩家的方法。 具體目標是聯盟中心控制器AllyCenterController,它實現了通知notify()方法,該方法將隊友call的消息傳達給玩家列表里的其余隊友,并作出相應的響應。

3.0.公共頭文件

通過一個枚舉類型來定義兩種消息類型,即發現物資和求助

#ifndef __COMMON_H__
#define __COMMON_H__enum INFO_TYPE{NONE,RESOURCE,HELP
};#endif //__COMMON_H__

3.1.觀察者

3.1.1.抽象觀察者Observer

class Observer {
public:virtual ~Observer() = default;virtual void call(INFO_TYPE, AllyCenter* ac) = 0;const std::string& getName() const { return name; }void setName(std::string n){ name = std::move(n); }protected:std::string name{"none"};
};

3.1.2.具體觀察者Player

// final: Player 不能再有子類
class Player final : public Observer {
public:Player() = default;explicit Player(std::string n) { setName(std::move(n)); }void call(INFO_TYPE, AllyCenter* ac) override; // 這里只聲明void help() const;void come() const;
};

3.2.目標類

3.2.1.抽象目標AllyCenter

// 前向聲明
// 不做前向聲明時,兩邊的頭文件往往互相 #include,形成“包含環”。 由于編譯是單向讀取的
class Observer;
// class Player;// 抽象目標:聯盟中心
class AllyCenter {
public:AllyCenter();virtual ~AllyCenter() {}// 聲明通知方法virtual void notify(INFO_TYPE infoType, const std::string& name) = 0;// 加入玩家void join(Observer *player);// 移除玩家void remove(Observer *player);protected:// 玩家列表std::vector<Observer*> playerList;
};

3.2.2.具體目標AllyCenterController

// 具體目標
class AllyCenterController : public AllyCenter {
public:AllyCenterController();// 實現通知方法void notify(INFO_TYPE infoType, const std::string& name) override;
};

循環包含

“循環包含”(include cycle)是指:頭文件彼此相互 #include(直接或間接 A→B→A)。
預處理展開時會卡在一個環里——雖然 include guard 會阻止“無限展開”,但副作用是某一邊在需要完整類型時只看到了前半張臉,于是報“未知類型 / 不完全類型(incomplete type)”之類的編譯錯誤。

錯誤示例

// A.h
#ifndef A_H
#define A_H
#include "B.h"                 // A 想用 B
struct A { B b; };             // ← 按值成員,必須要 B 的完整定義
#endif// B.h
#ifndef B_H
#define B_H
#include "A.h"                 // B 又包含 A
struct B { A a; };             // ← 也按值成員,需要 A 的完整定義
#endif

編譯時流程大致是:編譯器讀 A.h → 進 B.h → 試圖再進 A.h,但被 include guard 攔住,于是此時 B.h 里看不到 A 的完整定義,馬上報錯(反過來順序也一樣)。

“前向聲明什么時候不夠、必須 #include 對方頭?”

核心在于**“不完全類型 vs 完全類型”**。class X; 只是告訴編譯器“有個類型 X”,但看不到它的大小、布局和成員。凡是需要知道大小/布局/成員或能析構的場合,都必須拿到完整定義(所以要 #include “X.h”)。
在這里插入圖片描述
在這里插入圖片描述

3.3.客戶端代碼示例及效果

#include <cstdio>
#include <memory>
#include "Observer.h"
#include "AllyCenter.h"int main() {// 創建一個戰隊auto controller = std::make_unique<AllyCenterController>();// 創建4個玩家,并加入戰隊auto Jungle     = std::make_unique<Player>("Jungle");auto Single     = std::make_unique<Player>("Single");auto Jianmengtu = std::make_unique<Player>("Diego");auto SillyDog   = std::make_unique<Player>("Richard");controller->join(Jungle.get());controller->join(Single.get());controller->join(Jianmengtu.get());controller->join(SillyDog.get());std::puts("");Jungle->call(RESOURCE, controller.get());std::puts("");SillyDog->call(HELP, controller.get());std::puts("");#ifdef _WIN32system("pause");
#endifreturn 0; // 智能指針自動釋放
}

上述代碼運行結果如下圖:
在這里插入圖片描述

4.觀察者模式的應用

觀察者模式是一種使用頻率非常高的設計模式,幾乎無處不在。凡是涉及一對一、一對多的對象交互場景,都可以使用觀察者會模式。比如購物車,瀏覽商品時,往購物車里添加一件商品,會引起UI多方面的變化(購物車里商品數量、對應商鋪的顯示、價格的顯示等);各種編程語言的GUI事件處理的實現;所有的瀏覽器事件(mouseover,keypress等)都是使用觀察者模式的例子。

5.總結

在這里插入圖片描述

之后我會持續更新,如果喜歡我的文章,請記得一鍵三連哦,點贊關注收藏,你的每一個贊每一份關注每一次收藏都將是我前進路上的無限動力 !!!↖(▔▽▔)↗感謝支持!

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

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

相關文章

CA證書、SSL加速器、HTTPS、HTTP和域名之間的關系

理解CA證書、SSL加速器、HTTPS、HTTP和域名之間的關系對于構建安全、高效的網站至關重要。它們共同構成了現代安全網絡通信的基礎。下面是它們各自的概念以及它們之間的關系&#xff1a;域名 概念&#xff1a; 人類可讀的網站地址&#xff08;如 www.example.com&#xff09;。…

3D感知多模態(圖像、雷達感知)

一.BEVFusion1.簡要介紹BEV是一個俯視空間&#xff0c;Fusion做的就是融合&#xff0c;這里指的就是圖像和點云的融合。那如何把圖像和點云融合在一起&#xff1f;認為融合方法有三種&#xff1a;a.point level fusion:點集的融合&#xff0c;從點云中采樣一些點,再根據相機的內…

STM32 HAL庫驅動W25QXX Flash

STM32 HAL庫驅動W25QXX Flash 1. 概述 W25QXX系列是一種SPI接口的Flash存儲器&#xff0c;廣泛應用于嵌入式系統中作為數據存儲設備。本文檔詳細介紹了基于STM32 HAL庫的W25QXX Flash驅動實現&#xff0c;包括硬件連接、驅動函數實現以及使用示例。 項目源碼倉庫&#xff1a…

Vivado自定義IP核學習筆記

文章目錄【1】創建一個新的IP核【2】實現功能【3】編輯IP核【4】IP封裝【5】創建Vivado工程【1】創建一個新的IP核 1.1 打開Vivado->點擊【Tasks->Manage IP->New IP Location】->彈出窗口1.2 【Next】->設置IP屬性->【Finish】->【OK】 【IP Location】…

【面試】高級開發面試場景題

1、如何保證MySql到ES的數據一致性? 答:ES是一個開元分布式搜索和分析引擎、它提供了全文搜索、結構化搜索分析以及這些組合的能力。 全文搜索能力:ES支持復雜的搜索能力,包括模糊匹配、短語查詢、布爾查詢等,并且可以快速的返回結果 實時數據分析:實時數據分析,支持對…

《 慢 SQL 分析與 SQL 優化實戰指南》

&#x1f50d; 慢 SQL 分析與 SQL 優化實戰指南、 &#x1f9e0;前言 在數據庫性能調優中&#xff0c;慢 SQL 是性能瓶頸的常見元兇。 一次慢查詢可能會拖垮整個業務線程池&#xff0c;甚至引發鎖等待、雪崩效應。 對后端開發與 DBA 而言&#xff0c;快速定位并優化慢 SQL&am…

C#中如何運用JWT用戶認證

一、JWT概述JSON Web Token&#xff08;JWT&#xff09;是一種輕量級的身份認證機制&#xff0c;廣泛應用于分布式系統中的用戶認證。它通過緊湊的JSON格式存儲用戶身份信息&#xff0c;并使用數字簽名確保信息的完整性和真實性。與傳統的基于Session的認證相比&#xff0c;JWT…

Hibernate 使用詳解

在現代的Java開發中&#xff0c;數據持久化是一個至關重要的環節。而在眾多持久化框架中&#xff0c;Hibernate以其強大的功能和靈活性&#xff0c;成為了開發者們的首選工具。本文將詳細介紹Hibernate的原理、實現過程以及其使用方法&#xff0c;希望能為廣大開發者提供一些有…

【圖像算法 - 13】基于 YOLO12 與 OpenCV 的實時目標點擊跟蹤系統(系統介紹 + 源碼詳細)

基于 YOLO12 與 OpenCV 的實時點擊目標跟蹤系統 在計算機視覺領域&#xff0c;目標檢測與跟蹤是兩個核心任務。本文將介紹一個結合 YOLO 目標檢測模型與 OpenCV 跟蹤算法的實時目標跟蹤系統&#xff0c;該系統允許用戶通過鼠標交互選擇特定目標進行持續跟蹤&#xff0c;支持多…

【數據庫】 MySQL 表的操作詳解

在 MySQL 數據庫的日常開發與維護中&#xff0c;表的操作是最基礎且最常用的部分。本文將從 創建表、查看表結構、修改表 以及 刪除表 等方面進行詳細講解&#xff0c;并附上對應的 SQL 語句示例&#xff0c;方便在實際項目中直接應用。一、創建表 1.1 創建表語法 CREATE TABLE…

DiT: Transformer上的擴散模型

論文&#xff08;ICCV 2023&#xff09;&#xff1a;Scalable Diffusion Models with Transformers 代碼和工程網頁&#xff1a;https://www.wpeebles.com/DiT.html DiTs&#xff08;Diffusion Transformers&#xff09;是首個基于Transformer架構的擴散模型&#xff01;它在…

MySQL 索引:索引為什么使用 B+樹?(詳解B樹、B+樹)

文章目錄一、二叉查找樹(BST)&#xff1a;不平衡二、平衡二叉樹(AVL)&#xff1a;旋轉耗時三、紅黑樹&#xff1a;樹太高由一個例子總結索引的特點基于哈希表實現的哈希索引高效的查找方式&#xff1a;二分查找基于二分查找思想的二叉查找樹升級版的BST樹&#xff1a;AVL 樹四、…

ESP32入門開發·VScode空白項目搭建·點亮一顆LED燈

目錄 1. 環境搭建 2. 創建項目 3. 調試相關介紹 4. 代碼編寫 4.1 包含頭文件 4.2 引腳配置 4.3 設置輸出電平 4.4 延時函數 4.5 調試 1. 環境搭建 默認已經搭建好環境&#xff0c;如果未搭建好可參考&#xff1a; ESP32入門開發Windows平臺下開發環境的搭建…

ONLYOFFICE AI 智能體上線!與編輯器、新的 AI 提供商等進行智能交互

ONLYOFFICE AI 插件?迎來重要更新&#xff0c;帶來了新功能和更智能的交互體驗。隨著 AI 智能體&#xff08;現為測試版&#xff09;的上線、帶來更多 AI 提供商支持以及其他新功能&#xff0c;AI 插件已經成為功能強大的文檔智能助理。 關于 ONLYOFFICE ONLYOFFICE 文檔是多…

【C++進階學習】第十一彈——C++11(上)——右值引用和移動語義

前言&#xff1a; 前面我們已經將C的重點語法講的大差不差了&#xff0c;但是在C11版本之后&#xff0c;又出來了很多新的語法&#xff0c;其中有一些作用還是非常大的&#xff0c;今天我們就先來學習其中一個很重要的點——右值引用以及它所擴展的移動定義 目錄 一、左值引用和…

【IoTDB】363萬點/秒寫入!IoTDB憑何領跑工業時序數據庫賽道?

【作者主頁】Francek Chen 【專欄介紹】???大數據與數據庫應用??? 大數據是規模龐大、類型多樣且增長迅速的數據集合&#xff0c;需特殊技術處理分析以挖掘價值。數據庫作為數據管理的關鍵工具&#xff0c;具備高效存儲、精準查詢與安全維護能力。二者緊密結合&#xff0…

IEEE 2025 | 重磅開源!SLAM框架用“法向量+LRU緩存”,將三維重建效率飆升72%!

一、前言 當前研究領域在基于擴散模型的文本到圖像生成技術方面取得了顯著進展&#xff0c;尤其在視覺條件控制方面。然而&#xff0c;現有方法&#xff08;如ControlNet&#xff09;在組合多個視覺條件時存在明顯不足&#xff0c;主要表現為獨立控制分支在去噪過程中容易引入…

無人機遙控器教練模式技術要點

一、技術要點1.控制權仲裁機制&#xff1a;核心功能&#xff1a;清晰定義主控權歸屬邏輯&#xff08;默認為學員&#xff0c;但教練隨時可接管&#xff09;。切換方式&#xff1a;通常通過教練遙控器上的物理開關&#xff08;瞬時或鎖定型&#xff09;或軟件按鈕觸發。切換邏輯…

【跨服務器的數據自動化下載--安裝公鑰,免密下載】

跨服務器的數據自動化下載功能介紹&#xff1a;上代碼&#xff1a;發現好久沒寫csdn了&#xff0c;說多了都是淚~~ 以后會更新一些自動化工作的腳本or 小tricks&#xff0c;歡迎交流。分享一個最近在業務上寫的較為實用的自動化腳本&#xff0c;可以批量從遠端服務器下載指定數…

C++-->stl: list的使用

前言list的認識list是可以在固定時間&#xff08;O&#xff08;1&#xff09;&#xff09;內在任意位置進行插入和刪除的序列式容器&#xff0c;并且該容器可以前后雙向迭代。 2. list的底層是雙向鏈表結構&#xff0c;雙向鏈表中每個元素存儲在互不相關的獨立節點中&#xff0…