概述
描述
-
定義對象間的一種一對多依賴關系,使得每當一個對象狀態發生改變時,其相關依賴對象皆得到通知並被自動更新。觀察者模式又叫做
- 發布-訂閱(Publish/Subscribe)模式
- 模型-視圖(Model/View)模式
- 源-監聽器(Source/Listener)模式
- 從屬者(Dependents)模式。
套路
- 抽象目標(Subject)
被觀察的目標,每個目標都可以有任何數量的觀察者。抽象目標提供一個接口,可以增加和刪除觀察者對象。 - 具體目標(ConcreteSubject)
具體目標持有狀態,當內部狀態改變時,向所有觀察者發出通知。同時向觀察者提供用於查詢狀態的接口。 - 抽象觀察者(Observer)
為所有的具體觀察者定義一個接口,在得到目標通知時更新自己。 - 具體觀察者(ConcreteObserver)
持有具體目標的引用。實現抽象觀察者角色所要求的更新接口,以便使本身的狀態與目標狀態協調。
使用場景
- 一個抽象模型有兩個方面,其中一個方面依賴於另一個方面。將這些方面封裝在獨立的對象中使它們可以各自獨立地改變和復用。
- 一個對象的改變將導致其他一個或多個對象也發生改變,而不知道具體有多少對象將發生改變,可以降低對象之間的耦合度。
- 一個對象必須通知其他對象,而並不知道這些對象是誰。
- 需要在系統中創建一個觸發鏈,A對象的行為將影響B對象,B對象的行為將影響C對象……,可以使用觀察者模式創建一種鏈式觸發機制。
- 示例,凡是涉及到一對一或者一對多的對象交互場景都可以使用觀察者模式。
- 電子商務網站可以在執行發送操作后給用戶多個發送商品打折信息
- 可用於各種消息分發,如游戲活動通知、隊伍副本攻略進度
- UE4中的委托代理、藍圖中的事件調度器dispatcher、按鍵事件綁定、碰撞事件綁定
- MVC 架構模式就應用了觀察者模式——多個 view 注冊監聽 model
優缺點
- 優點
- 觀察者模式可以實現表示層和數據邏輯層的分離,並定義了穩定的消息更新傳遞機制,抽象了更新接口,使得可以有各種各樣不同的表示層作為具體觀察者角色。
- 觀察者模式在觀察目標和觀察者之間建立一個抽象的耦合。
- 觀察者模式支持廣播通信。
- 觀察者模式符合“開閉原則”的要求。
- 缺點
- 如果一個觀察目標對象有很多直接和間接的觀察者的話,將所有的觀察者都通知到會花費很多時間。
- 如果在觀察者和觀察目標之間有循環依賴的話,觀察目標會觸發它們之間進行循環調用,可能導致系統崩潰。
- 觀察者模式沒有相應的機制讓觀察者知道所觀察的目標對象是怎么發生變化的,而僅僅只是知道觀察目標發生了變化。
- 訂閱后如果不進行取消訂閱操作容易引起內存泄露
UE4 實踐
-
不使用 UE4 中的委托代理,自己實現一個消息通知
-
做一個核輻射監測和救援人員受其影響的例子(代碼都寫在頭文件中,有些聲明問題需自己解決)
-
創建觀察者抽象類、具體類
// 抽象觀察者類 UCLASS(Abstract) class DESIGNPATTERNS_API UObserver : public UObject { GENERATED_BODY() public: virtual void Update() PURE_VIRTUAL(UObserver::IsValid, ); }; // 具體觀察者類 —— 工作人員 UCLASS(Blueprintable, BlueprintType) class DESIGNPATTERNS_API UWorker : public UObserver { GENERATED_BODY() public: // 設置核輻射監測對象 void SetNuclearRadiation(UNuclearRadiation* pNuclearRadiation) { m_pNuclearRadiation = pNuclearRadiation; } virtual void Update() override { if (m_pNuclearRadiation) { int32 CurrentLevel = m_pNuclearRadiation->GetRadiationDegree(); if (CurrentLevel < 1) { UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__" %s報告: 生命體征正常"), *this->GetName()); } else if (CurrentLevel < 2) { UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__" %s報告: 輕級損傷"), *this->GetName()); } else if (CurrentLevel < 4) { UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__" %s報告: 中級損傷"), *this->GetName()); } else if (CurrentLevel < 6) { UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__" %s報告: 重級損傷"), *this->GetName()); } else if (CurrentLevel < 8) { UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__" %s報告: 極重級損傷"), *this->GetName()); } else { UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__" %s已無生命體征"), *this->GetName()); this->ConditionalBeginDestroy(); // 不要在此處取消訂閱,否則影響TArray遍歷 } } } private: UNuclearRadiation* m_pNuclearRadiation; };
-
創建目標抽象類 、具體類
- 觀察者可以用TArray 或者 TMap 存儲
// 抽象目標類 UCLASS(Abstract) class DESIGNPATTERNS_API USubject : public UObject { GENERATED_BODY() public: // 添加訂閱者 virtual void BindNotify(UObserver* Observer) { if (!ObserverList.Contains(Observer)) { ObserverList.Add(Observer); UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__" %s 已訂閱"), *Observer->GetName()); // 第一次綁定 獲取當前數據 Observer->Update(); } } // 移除訂閱者 virtual void UnbindNotify(UObserver* Observer) { if (ObserverList.Contains(Observer)) { UE_LOG(LogTemp, Warning, TEXT(__FUNCTION__" %s 已取消訂閱"), *Observer->GetName()); ObserverList.Remove(Observer); } } // 通知 virtual void Notify() { for (auto It = ObserverList.CreateConstIterator(); It; It++) { if (IsValid(*It)) { (*It)->Update(); } } } protected: TArray<UObserver*> ObserverList; }; // 具體目標類 —— 輻射地帶、輻射監測 UCLASS(Blueprintable, BlueprintType) class DESIGNPATTERNS_API UNuclearRadiation : public USubject { GENERATED_BODY() public: // 更新輻射程度 void UpdateRadiationDegree(int32 pLevel) { m_pLevel = pLevel; Notify(); } // 獲取輻射程度 int32 GetRadiationDegree() { return m_pLevel; } private: // 輻射程度 1-9級 int32 m_pLevel; };
-
調用測試
// 調用測試用的Actor UCLASS() class DESIGNPATTERNS_API AObserverActor : public AActor { GENERATED_BODY() public: void BeginPlay() override { // 創建核輻射監測 UNuclearRadiation* NuclearRadiation = NewObject<UNuclearRadiation>(); // 創建救援人員、監測核輻射程度 UWorker* Worker0 = NewObject<UWorker>(); Worker0->SetNuclearRadiation(NuclearRadiation); NuclearRadiation->BindNotify(Worker0); UWorker* Worker1 = NewObject<UWorker>(); Worker1->SetNuclearRadiation(NuclearRadiation); NuclearRadiation->BindNotify(Worker1); UWorker* Worker2 = NewObject<UWorker>(); Worker2->SetNuclearRadiation(NuclearRadiation); NuclearRadiation->BindNotify(Worker2); // 核輻射升級 NuclearRadiation->UpdateRadiationDegree(1); NuclearRadiation->UpdateRadiationDegree(2); NuclearRadiation->UpdateRadiationDegree(3); // 有人先出來了,取消核輻射繼續影響 NuclearRadiation->UnbindNotify(Worker2); // 核輻射繼續升級 NuclearRadiation->UpdateRadiationDegree(5); NuclearRadiation->UpdateRadiationDegree(7); NuclearRadiation->UpdateRadiationDegree(9); GEngine->ForceGarbageCollection(true); } };
-
調式輸出
LogTemp: Warning: USubject::BindNotify Worker_0 已訂閱 LogTemp: Warning: UWorker::Update Worker_0報告: 生命體征正常 LogTemp: Warning: USubject::BindNotify Worker_1 已訂閱 LogTemp: Warning: UWorker::Update Worker_1報告: 生命體征正常 LogTemp: Warning: USubject::BindNotify Worker_2 已訂閱 LogTemp: Warning: UWorker::Update Worker_2報告: 生命體征正常 LogTemp: Warning: UWorker::Update Worker_0報告: 輕級損傷 LogTemp: Warning: UWorker::Update Worker_1報告: 輕級損傷 LogTemp: Warning: UWorker::Update Worker_2報告: 輕級損傷 LogTemp: Warning: UWorker::Update Worker_0報告: 中級損傷 LogTemp: Warning: UWorker::Update Worker_1報告: 中級損傷 LogTemp: Warning: UWorker::Update Worker_2報告: 中級損傷 LogTemp: Warning: UWorker::Update Worker_0報告: 中級損傷 LogTemp: Warning: UWorker::Update Worker_1報告: 中級損傷 LogTemp: Warning: UWorker::Update Worker_2報告: 中級損傷 LogTemp: Warning: USubject::UnbindNotify Worker_2 已取消訂閱 LogTemp: Warning: UWorker::Update Worker_0報告: 重級損傷 LogTemp: Warning: UWorker::Update Worker_1報告: 重級損傷 LogTemp: Warning: UWorker::Update Worker_0報告: 極重級損傷 LogTemp: Warning: UWorker::Update Worker_1報告: 極重級損傷 LogTemp: Warning: UWorker::Update Worker_0已無生命體征 LogTemp: Warning: UWorker::Update Worker_1已無生命體征