《游戲編程模式》學習筆記(四) 觀察者模式 Observer Pattern

定義

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

這是定義,看不懂就看不懂吧,我接下來舉個例子慢慢說

為什么我們需要觀察者模式

我們看一個很簡單的需求,現在要你在游戲中加入成就系統,在物體墜落1000米的時候給玩家發一個成就勛章,你要這么做?

最直觀的方法就是,在游戲的物理系統那一部分中,加入這么一段代碼:

void Physics::updateEntity(Entity& entity)
{bool wasOnSurface = entity.isOnSurface();entity.accelerate(GRAVITY);entity.update();if (wasOnSurface && !entity.isOnSurface()){if (surface.height - entity.height > 1000){//解鎖成就unlockFallOffBridge();}}
}

咋一看是不是還行?就加了幾行而已。
那么如果我還要求你播放墜落音效呢?是不是還得這樣寫:

void Physics::updateEntity(Entity& entity)
{bool wasOnSurface = entity.isOnSurface();entity.accelerate(GRAVITY);entity.update();if (wasOnSurface && !entity.isOnSurface()){if (surface.height - entity.height > 1000){//解鎖成就unlockFallOffBridge();//播放音效playfallmusic();}}
}

這樣看也還行,那如果組長讓你根據物體撞擊不同的地面,播放不同的地面音效,那這段代碼是不是又得膨脹了:

void Physics::updateEntity(Entity& entity)
{bool wasOnSurface = entity.isOnSurface();entity.accelerate(GRAVITY);entity.update();if (wasOnSurface && !entity.isOnSurface()){if (surface.height - entity.height > 1000){//解鎖成就unlockFallOffBridge();//播放音效if (hitground){playhitgroundmusic();}if (hitwater){playhitwatermusic();}//.....}}
}

要知道,這可是在你的游戲的物理引擎中,我們并不想看到在處理撞擊代碼的線性代數時, 有出現關于成就系統,音效系統的調用是不?我們喜歡的是,照舊,讓關注游戲一部分的所有代碼集成到一塊。我們想要解耦物理系統和這些不相關的東西。

這就是觀察者模式出現的原因。 這讓代碼宣稱有趣的事情發生了,而不必關心到底是誰接受了通知。

一旦你使用了觀察者模式,你的代碼就會變成這樣:

void Physics::updateEntity(Entity& entity)
{bool wasOnSurface = entity.isOnSurface();entity.accelerate(GRAVITY);entity.update();if (wasOnSurface && !entity.isOnSurface()){notify(entity, EVENT_START_FALL);}
}

是不是簡潔了很多很多?比剛才那一大堆丑陋的代碼好看多了。

觀察者模式做的就是聲稱,“額,我不知道有誰感興趣,但是這個東西剛剛掉下去了。做你想做的事吧。”

可能有人會說,誒,這也沒有完全解耦啊。的確,物理引擎確實決定了要發送什么通知,所以這并沒有完全解耦。但在架構這個領域,通常只能讓系統變得更好,而不是完美。

如何構建觀察者模式?

最傳統的構建方式就是這樣,使用對象模式構建觀察者

我們先寫一個基礎的觀察者抽象基類

class Observer
{
public:virtual ~Observer() {}virtual void onNotify(const Entity& entity, Event event) = 0;
};

然后讓我們的成就系統和音效系統等想成為觀察者的系統都繼承這個基類:

class Achievements : public Observer
{
public:virtual void onNotify(const Entity& entity, Event event){switch (event){case EVENT_ENTITY_FELL:if (entity.isHero() && heroIsOnBridge_){unlock(ACHIEVEMENT_FELL_OFF_BRIDGE);}break;// 處理其他事件,更新heroIsOnBridge_變量……}}private:void unlock(Achievement achievement){// 如果還沒有解鎖,那就解鎖成就……}bool heroIsOnBridge_;
};

對于被觀察者,如物理系統中,我們只要讓它持有這個observer的指針就好了,一旦出現了某些事件,我們就給這些指針指向的observer發消息。
為了正式一點,讓所有可能的系統都成為被觀察者,我們寫一個叫subject的基類,讓所有想成為被觀察者的系統都可以繼承這個基類來成為被觀察者。

class Subject
{
public:void addObserver(Observer* observer){// 添加到數組中……}void removeObserver(Observer* observer){// 從數組中移除……}void removeObserver(Observer* observer){// 從數組中移除……}
protected:void notify(const Entity& entity, Event event){for (int i = 0; i < numObservers_; i++){observers_[i]->onNotify(entity, event);}}private:Observer* observers_[MAX_OBSERVERS];int numObservers_;
};

我們可以看見,這里寫了一個觀察者數組,存了許多觀察者的指針,這是因為大部分情況下,被觀察者可能會有好多個觀察者觀察著它。然后我們也寫了一些方法來增刪這個數組。

然后就是面向對象的東西了,我們讓物理系統繼承這個基類

class Physics : public Subject
{
public:void updateEntity(Entity& entity);
};

現在,當物理引擎做了些值得關注的事情,它調用notify(),就像之前的例子。 它遍歷了觀察者列表,通知所有觀察者。
在這里插入圖片描述
恭喜你已經掌握了如何寫一個觀察者模式,你所看到的就是一個觀察者模式的全部。現在來回顧一下定義:

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

是不是有點明白了?

**

觀察者模式的使用場合

**

當一個抽象模式有兩個方面,其中一個方面依賴于另一個方面,需要將這兩個方面分別封裝到獨立的對象中,彼此獨立地改變和復用的時候。
當一個系統中一個對象的改變需要同時改變其他對象內容,但是又不知道待改變的對象到底有多少個的時候。
當一個對象的改變必須通知其他對象作出相應的變化,但是不能確定通知的對象是誰的時候。

觀察者模式的缺點:

  1. 由于觀察者模式調用了一些虛方法,終究會比靜態調用慢一些。
  2. 觀察者模式是同步的。 被觀察者直接調用了觀察者,這意味著直到所有觀察者的通知方法返回后, 被觀察者才會繼續自己的工作。觀察者會阻塞被觀察者的運行。
  3. 由于被觀察者維護了一個數組來存儲觀察者指針,在實際情況中一般會用動態數組而不是這次例子中的靜態數組。這樣就會做出太多的動態分配。解決方法還是有的,那就是使用鏈表而不是數組來存儲觀察者指針(反正你都得遍歷發通知,這倆差不多)。

原文鏈接:https://gpp.tkchu.me/observer.html

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

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

相關文章

PAT (Advanced Level) 甲級 1004 Counting Leaves

點此查看所有題目集 A family hierarchy is usually presented by a pedigree tree. Your job is to count those family members who have no child. Input Specification: Each input file contains one test case. Each case starts with a line containing 0<N<100, …

如何在iPhone手機上修改手機定位和模擬導航?

如何在iPhone手機上修改手機定位和模擬導航&#xff1f; English Location Simulator&#xff08;定位模擬工具&#xff09; 是一款功能強大的 macOS 應用&#xff0c;專為 iPhone 用戶設計&#xff0c;旨在修改手機定位并提供逼真的模擬導航體驗。無論是為了保護隱私、測試位…

Angular 獨立組件入門

Angular 獨立組件入門 如果你正在學習 Angular&#xff0c;那么你可能已經聽說過獨立組件&#xff08;Component&#xff09;。顧名思義&#xff0c;獨立組件就是可以獨立使用和管理的組件&#xff0c;它們能夠被包含在其他組件中或被其他組件引用。 在本文中&#xff0c;我們…

【Unity腳本開源】記錄鼠標按下的位置和移動的距離來進行物體的旋轉,并在鼠標釋放后將物體恢復到初始旋轉位置

??作者&#xff1a;白日參商 &#x1f935;?♂?個人主頁&#xff1a;白日參商主頁 ??堅持分析平時學習到的項目以及學習到的軟件開發知識&#xff0c;和大家一起努力呀&#xff01;&#xff01;&#xff01; &#x1f388;&#x1f388;加油&#xff01; 加油&#xff01…

go-安裝部署

一、安裝go 詳細安裝方式可以查看官網 # 下載 wget https://golang.google.cn/dl/go1.21.0.linux-amd64.tar.gz # 解壓縮 tar -xzf go1.21.0.linux-amd64.tar.gz # 遷移目錄 mv go /usr/local # 配置環境變量 export PATH$PATH:/usr/local/go/bin # 檢查go的版本 go version有…

Python中的字符串與字符編碼

Hello&#xff0c;這里是Token_w的博客&#xff0c;歡迎您的到來 今天文章講解的是Python中的字符串與字符編碼&#xff0c;其中有基礎的理論知識講解&#xff0c;也有實戰中的應用講解&#xff0c;希望對你有所幫助 整理不易&#xff0c;如對你有所幫助&#xff0c;希望能得到…

PDM/PLM系統建設

僅供學習使用&#xff0c;會隨時更新 工程機械跨生命周期數據管理系統 來源&#xff1a;清華大學 淺論企業PDM/PLM系統建設成功經驗 來源&#xff1a;e-works 作者&#xff1a;陳凡 https://articles.e-works.net.cn/pdm/article149572.htm 隨著“中國制造2025”強基工程戰略的…

張俊林:由ChatGPT反思大語言模型(LLM)的技術精要

轉自&#xff1a;https://mp.weixin.qq.com/s/eMrv15yOO0oYQ-o-wiuSyw 導讀&#xff1a;ChatGPT出現后驚喜或驚醒了很多人。驚喜是因為沒想到大型語言模型&#xff08;LLM,Large Language Model&#xff09;效果能好成這樣&#xff1b;驚醒是頓悟到我們對LLM的認知及發展理念&a…

Elisp之獲取PC電池狀態(二十八)

簡介&#xff1a; CSDN博客專家&#xff0c;專注Android/Linux系統&#xff0c;分享多mic語音方案、音視頻、編解碼等技術&#xff0c;與大家一起成長&#xff01; 優質專欄&#xff1a;Audio工程師進階系列【原創干貨持續更新中……】&#x1f680; 人生格言&#xff1a; 人生…

Linux學習筆記

grep -r "root" /var/log/messages #查找一個目錄下所有包含特定字符竄的文件 grep -r "root" /var/log/messages |wc -l #如何計算一個文本文件中某個單詞出現的次數&#xff1f; du -sh /var/log #如何統計一個目錄下所有文件和子目錄的總大小&#xff1…

博客摘錄「 佛祖保佑,永無bug——springboot啟動圖案的修改方法」2023年6月8日

挺有意思的。佛祖保佑永無BUG 神獸護體 代碼注釋(各種版本)_風流 少年的博客-CSDN博客

ArcGIS Pro 基礎安裝與配置介紹

ArcGIS Pro ArcGIS Pro作為ESRI面向新時代的GIS產品&#xff0c;它在原有的ArcGIS平臺上繼承了傳統桌面軟件&#xff08;ArcMap&#xff09;的強大的數據管理、制圖、空間分析等能力&#xff0c;還具有其獨有的特色功能&#xff0c;例如二三維融合、大數據、矢量切片制作及發布…

django中實現事務/django實現悲觀鎖樂觀鎖案例

django中實現事務的幾種方式 # 1 全局開啟事務---> 全局開啟事務&#xff0c;綁定的是http請求響應整個過程DATABASES {default: {#全局開啟事務&#xff0c;綁定的是http請求響應整個過程ATOMIC_REQUESTS: True, }}from django.db import transaction# 局部禁用事務trans…

Unity 鼠標控制 UI 放大、縮小、拖拽

文章目錄 1. 代碼2. 測試場景 1. 代碼 using UnityEngine; using UnityEngine.UI; using UnityEngine.EventSystems;public class UIDragZoom : MonoBehaviour, IDragHandler, IScrollHandler {private Vector2 originalSize;private Vector2 originalPosition;private RectTr…

css3 瀑布流布局遇見截斷下一列展示后半截現象

css3 瀑布流布局遇見截斷下一列展示后半截現象 注&#xff1a;css3實現瀑布流布局簡直不要太香&#xff5e;&#xff5e;&#xff5e;&#xff5e;&#xff5e; 場景-在uniapp項目中 當瀑布流布局column-grap:10px 相鄰兩列之間的間隙為10px&#xff0c;column-count:2,2列展…

面試之快速學習C++11-完美轉發,nullptr, shared_ptr,unique_ptr,weak_ptr,shared_from_this

完美轉發及其實現 函數模版可以將自己的參數完美地轉發給內部調用的其他函數。所謂完美&#xff0c;即不僅能準確地轉發參數的值&#xff0c;還能保證被轉發參數的左右值屬性不變引用折疊&#xff1a;如果任一引用為左值引用&#xff0c;則結果為左值引用&#xff0c;否則為右…

在阿里云服務器上安裝Microsoft SharePoint 2016流程

本教程阿里云百科分享如何在阿里云ECS上搭建Microsoft SharePoint 2016。Microsoft SharePoint是Microsoft SharePoint Portal Server的簡稱。SharePoint Portal Server是一個門戶站點&#xff0c;使得企業能夠開發出智能的門戶站點。 目錄 背景信息 步驟一&#xff1a;添加…

【Leetcode 30天Pandas挑戰】學習記錄 下

題目列表&#xff1a; 數據統計:2082. The Number of Rich Customers1173. Immediate Food Delivery I1907. Count Salary Categories 數據分組1741. Find Total Time Spent by Each Employee511. Game Play Analysis I2356. Number of Unique Subjects Taught by Each Teacher…

無涯教程-Perl - setgrent函數

描述 此功能將枚舉設置(或重置)到組條目集的開頭。該函數應在第一次調用getgrent之前調用。 語法 以下是此函數的簡單語法- setgrent返回值 此函數不返回任何值。 例 以下是顯示其基本用法的示例代碼- #!/usr/bin/perl -wwhile( ($name,$passwd,$gid,$members)getgrent…

ide internal errors【bug】

ide internal errors【bug】 前言版權ide internal errors錯誤產生相關資源解決1解決2 設置虛擬內存最后 前言 2023-8-15 12:36:59 以下內容源自《【bug】》 僅供學習交流使用 版權 禁止其他平臺發布時刪除以下此話 本文首次發布于CSDN平臺 作者是CSDN日星月云 博客主頁是h…