1. 程式人生 > >C++之觀察者模式(訂閱-釋出模式)

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傳送一個對應的訊息即可。