C++之觀察者模式(訂閱-釋出模式)
定義:
觀察者模式定義了一種一對多的依賴關係,讓多個觀察者物件同時監聽某一個主題物件。這個主題物件在狀態發生變化時,會通知所有觀察者物件,使它們能夠自動更新自己。
最常見的一個例子就是:對同一組資料進行統計分析時候,我們希望能夠提供多種形式的表示(例如以表格進行統計顯示、柱狀圖統計顯示、百分比統計顯示等)。這些表示都依賴於同一組資料,我們當然需要當資料改變的時候,所有的統計的顯示都能夠同時改變。Observer模式就是解決了這一個問題。
UML類圖:
這裡的目標Subject提供依賴於它的觀察者Observer的註冊(Attach)和登出(Detach)操作,並且提供了使得依賴於它的所有觀察者同步的操作(Notify)。觀察者Observer則提供一個Update操作,注意這裡的Observer的Update操作並不在Observer改變了Subject目標狀態的時候就對自己進行更新,這個更新操作要延遲到Subject物件發出Notify通知所有Observer進行修改(呼叫Update)。
程式碼展示
主題程式碼:
//subject.h
#ifndef __SUBJECT__H__
#define __SUBJECT__H__
#include "Observer.h"
#include <list>
#include <string>
using namespace std;
class Observer;
class Subject
{
public:
Subject();
virtual ~Subject();
virtual void attach(Observer* obs);
virtual void detach(Observer* obs);
virtual void Notify();
virtual void setState(const string& str) = 0;
virtual string getState() = 0;
private:
list<Observer*>* _observers;
};
class ConcreteSubject : public Subject {
public:
ConcreteSubject();
~ConcreteSubject();
void setState(const string& str);
string getState();
private:
string state;
};
#endif
//subject.cpp
#include "Subject.h"
#include <iostream>
#include <list>
using namespace std;
Subject::Subject() {
_observers = new list<Observer*>;
}
Subject::~Subject() {
}
void Subject::attach(Observer* obs) {
if (obs != NULL) {
_observers->push_front(obs);
}
}
void Subject::detach(Observer* obs) {
if (obs != NULL) {
_observers->remove(obs);
}
}
void Subject::Notify() {
//遍歷更新
list<Observer*>::iterator iter;
for (iter = _observers->begin(); iter != _observers->end(); iter++) {
(*iter)->update(this);
}
}
ConcreteSubject::ConcreteSubject() {
this->state = '\0';
}
ConcreteSubject::~ConcreteSubject() {
}
string ConcreteSubject::getState() {
return this->state;
}
void ConcreteSubject::setState(const string& str) {
this->state = str;
}
觀察者程式碼:
//Observer.h
#ifndef __OBSERVER__H__
#define __OBSERVER__H__
#include "Subject.h"
#include <string>
using namespace std;
class Subject;
class Observer {
public:
Observer();
virtual ~Observer();
virtual void update(Subject* subject) = 0;
virtual void printInfo() = 0;
public:
std::string state;
};
class ConcreteObserverA:public Observer
{
public:
ConcreteObserverA(Subject* sub);
~ConcreteObserverA();
void update(Subject* sub);
void printInfo();
private:
Subject* _sub;
};
class ConcreteObserverB :public Observer
{
public:
ConcreteObserverB(Subject* sub);
~ConcreteObserverB();
void update(Subject* sub);
void printInfo();
private:
Subject* _sub;
};
#endif
//Observer.cpp
#include "Observer.h"
#include <list>
#include <iostream>
using namespace std;
Observer::Observer() {
state = '\0';
}
Observer::~Observer() {
}
ConcreteObserverA::ConcreteObserverA(Subject* sub) {
_sub = sub;
_sub->attach(this);
}
ConcreteObserverB::ConcreteObserverB(Subject* sub) {
_sub = sub;
_sub->attach(this);
}
ConcreteObserverA::~ConcreteObserverA() {
_sub->detach(this);
if (_sub != 0) {
delete _sub;
}
}
ConcreteObserverB::~ConcreteObserverB() {
_sub->detach(this);
if (_sub != 0) {
delete _sub;
}
}
void ConcreteObserverA::update(Subject* subject) {
state = subject->getState();
this->printInfo();
}
void ConcreteObserverB::update(Subject* subject) {
state = subject->getState();
this->printInfo();
}
void ConcreteObserverA::printInfo() {
// string str = _sub->getState();
cout << "ConcreteObserverA observer.... " << _sub->getState() << endl;
}
void ConcreteObserverB::printInfo() {
std::cout << "ConcreteObserverB observer.... " << _sub->getState() << std::endl;
}
//下面來使用一下這個模式
#include "Observer.h"
#include "Subject.h"
#include <iostream>
using namespace std;
int main() {
Subject* sub = new ConcreteSubject();
ConcreteObserverA* obA = new ConcreteObserverA(sub);
ConcreteObserverB* obB = new ConcreteObserverB(sub);
obA->printInfo();
obB->printInfo();
//開始更新發 布
sub->setState("大家好,我現在已經發布了最新訊息");
obA->update(sub);
obB->update(sub);
}
執行結果如下:
觀察者模式使用場合:
一個抽象模型有兩個方面,其中一個方面依賴於另一個方面。將這些方面封裝在獨立的物件中使它們可以各自獨立地改變和複用。
一個物件的改變將導致其他一個或多個物件也發生改變,而不知道具體有多少物件將發生改變,可以降低物件之間的耦合度。
一個物件必須通知其他物件,而並不知道這些物件是誰。需要在系統中建立一個觸發鏈,A物件的行為將影響B物件,B物件的行為將影響C物件……,可以使用觀察者模式建立一種鏈式觸發機制。
優缺點:
優點:
- 實現了目標物件和觀察者之間的抽象耦合,在本例中,則是實現了訊息與觀察者的抽象耦合。可以定義一種訊息與訊息處理物件的一對多的關係,而不用擔心彼此的實現細節。
缺點:
- 如果一個被觀察者物件有很多的直接和間接的觀察者的話,將所有的觀察者都通知到會花費很多時間。
遊戲開發中如何運用此模式
我們考慮cocos2d-x中一個非常典型的應用場景,你的GameScene裡面有兩個layer,一個gameLayer,它包含了遊戲中的物件,比如玩家、敵人等。另一個層是HudLayer,它包含了遊戲中顯示分數、生命值等資訊。如何讓這兩個層相互通訊。
1)第一種辦法,你可以讓gameLayer包含一個hudLayer的引用,同時也可以讓hudLayer包含一個gameLayer的引用。注意!這裡問題出現了,如果兩個類都包含彼此的強引用(所謂強引用就是retain),就會引起迴圈引用的情況,如果其中一個類包含的是弱引用,問題就不會出現。迴圈引用是使用引用計數管理記憶體的一個致命弱點,會導致資源永遠得不到釋放,而且查錯起來非常麻煩。
2)第二種辦法,把gameScene做成一個單例,同時讓gameScene包含gameLayer和hudLayer的弱引用,這樣就可以直接通過[GameScene sharedInstance].gameLayer或者[GameScene sharedInstance].hudLayer來訪問了。
3)第三種辦法,使用gameLayer->getParent()獲得gameScene,再使用gameScene來獲得hudLayer。
4)第四種辦法,使用CCNotificationCenter。當hudLayer註冊它感興趣的訊息,當gameLayer需要通知hudLayer的時候,只需通過CCNotificationCenter傳送一個對應的訊息即可。