在日常生活中,交通信號燈指揮者日益擁擠的城市交通。紅燈亮,汽車停止;綠燈亮,汽車繼續前行;在這個過程中,交通信號燈是汽車的觀察目標,而汽車則是觀察者。隨着交通信號燈的變化,汽車的行為也會隨之變化,一盞交通信號燈可以指揮多輛汽車。在軟件系統中,有些對象之間也存在類似交通信號燈和汽車之間的關系,一個對象的狀態或行為的變化將會導致其他對象的狀態或者行為也發生改變,它們之間將產生聯動,正所謂牽一發而動全身。為了更好地描述對象之間存在的這種一對多的聯動,觀察者模式應運而生。
一、多人聯機對戰游戲的設計
需求背景:M公司欲開發一款多人聯機對戰游戲,在游戲中,多個游戲玩家可以加入同一戰隊組成聯盟,當戰隊中某一成員收到敵人攻擊時將給所有其他盟友發送通知,盟友收到通知后將作出響應。M公司開發人員需要提供一個設計方案來實現戰隊成員之間的聯動。
M公司開發人員通過分析,發現在該系統中戰隊成員之間的聯動過程可以簡單描述如下:
聯盟成員收到攻擊 => 發送通知給盟友 => 盟友作出響應
如果每個聯盟成員都需要持有盟友的信息才能及時通知每一位盟友,因此這樣系統開銷較大。因此,M公司開發人員決定引入一個新角色“戰隊控制中心”來負責維護和管理每個戰隊所有成員的信息,如下圖所示:
二 觀察者模式
觀察者模式是一種使用頻率最高的設計模式之一,用於建立一種對象與對象之間的依賴關系,一個對象發生改變時將自動通知其他對象,其他對象將相應作出反應。
2.1 觀察者模式類圖
注意:由於Player需要向控制中心發送消息,控制中心又需要給所有玩家發送消息,在實現過程中存在頭文件相互引用的問題,特引入IControlCenter類。
2.2 代碼實現
(1)抽象觀察者類
class IObserver { public: IObserver(char *pName = NULL){} ~IObserver(){} virtual void Help() = 0; virtual void BeAttacked(IControlCenter *pControlCenter) = 0; };
(2)實體CPlayer類
class Player : public IObserver { public: Player(char *pName = NULL) { size_t nLen = strlen(pName); memcpy(m_pName, pName, nLen + 1); } ~Player(){} void Help() { cout << m_pName << ":" << "堅持住,馬上支援!" << endl; } void BeAttacked(IControlCenter *pControlCenter) { cout << m_pName << ":" << "我被攻擊,請求支援!" << endl; pControlCenter->NotifyObserver(m_pName); } private: char m_pName[NAME_LENGTH]; };
(3)抽象控制中心
#pragma once class IControlCenter { public: IControlCenter(){} ~IControlCenter(){} virtual void NotifyObserver(char* pName) = 0; };
(4)實體控制中心
#pragma once #include <map> using namespace std; #include "IControlCenter.h" #include "IObserver.h" class CControlCenter:public IControlCenter { public: CControlCenter(){} ~CControlCenter(){} void Regesiter(char* pName, IObserver* pObserver) { m_ObserverMap[pName] = pObserver; cout << "通知:" << pName <<" " << "加入戰隊!" << endl; } void UnRegister(char *pName) { map<char*, IObserver*>::iterator iter; for (iter = m_ObserverMap.begin(); iter != m_ObserverMap.end(); iter ++) { if (0 == strcmp(pName, iter->first)) { m_ObserverMap.erase(iter); cout << pName <<" " << "離開戰隊!" << endl; break; } } } void NotifyObserver(char* pName) { cout << "通知:" << "盟友們,"<< pName <<"正在被攻擊" << endl; map<char*, IObserver*>::iterator iter; for (iter = m_ObserverMap.begin(); iter != m_ObserverMap.end(); iter ++) { if (0 != strcmp(pName, iter->first)) { IObserver *pPlayer = iter->second; pPlayer->Help(); } } } private: map<char*, IObserver*> m_ObserverMap; };
2.3 測試
(1)測試代碼
#include "stdio.h" #include "ControlCenter.h" #include "IObserver.h" void main() { CControlCenter *pControlCenter = new CControlCenter(); IObserver *pPlayer1 = new Player("張三"); IObserver *pPlayer2 = new Player("李四"); IObserver *pPlayer3 = new Player("王麻子"); pControlCenter->Regesiter("張三", pPlayer1); pControlCenter->Regesiter("李四", pPlayer2); pControlCenter->Regesiter("王麻子",pPlayer3); pPlayer1->BeAttacked(pControlCenter); }
(2)結果
三、觀察者模式與MVC
在當前流行的MVC(Model-View-Controller)結構中也應用了觀察者模式,它包含了3個角色:模型、視圖和控制器。其中,模型可對應觀察者模式中的觀察目標,而視圖則對應於觀察者,控制器充當二者之間的中介者。當模型層的數據發生改變時,視圖將會自動改變其顯示內容,如下圖所示:
四、觀察者模式總結
4.1 主要優點
(1)可以實現表示層和數據邏輯層的分離 => 各種不同的表示層可以充當具體觀察者
(2)支持廣播通信,觀察目標會向已注冊的觀察者對象發送通知 => 簡化一對多系統設計的難度
(3)增加新的觀察者無須修改原有系統代碼 => 滿足開閉原則
4.2 主要缺點
(1)如果一個觀察目標有很多直接和間接的觀察者 => 所有觀察者收到通知會花費大量時間
(2)如果觀察者和觀察目標之間存在循環依賴 => 可能導致系統崩潰
4.3 應用場景
(1)一個抽象模型有兩個方面,其中一個方面依賴於另一個方面 => 封裝起來使其獨立改變和復用
(2)一個對象的改變將導致一個或多個其他對象也發生改變,但並不知道具體有多少個對象將要發生改變 => 最熟悉的陌生人