大衛的Design Patterns學習筆記19:Observer

一、概述
Observer(觀察者)模式又被稱作發佈-訂閱(Publish-Subscribe)模式,用於定義對象間的一對多的依賴關係,當一個對象的狀態發生改變時,所有依賴於它的對象都得到通知並被自動更新。

二、結構
Observer模式的結構如下圖所示:

1、Observer模式類圖示意
上面的類圖中包括如下組成部分:
Subject(抽象主題)角色:主題角色把所有對觀察考對象的引用保存在一個聚集裏,每個主題都可以有任何數量的觀察者。抽象主題提供一個接口,可以增加和刪除觀察者對象,主題角色又叫做抽象被觀察者(Observable)角色,一般用一個抽象類或者一個接口實現。 
Observer(抽象觀察者)角色:爲所有的具體觀察者定義一個接口,在得到主題的通知時更新自己。這個接口叫做更新接口。抽象觀察者角色一般用一個抽象類或者一個接口實現。在這個示意性的實現中,更新接口只包含一個方法(即Update()方法),這個方法叫做更新方法。 
ConcreteSubject(具體主題)角色:將有關狀態存入具體現察者對象;在具體主題的內部狀態改變時,給所有登記過的觀察者發出通知。具體主題角色又叫做具體被觀察者角色(Concrete Observable)。具體主題角色通常用一個具體子類實現。 
ConcreteObserver(具體觀察者)角色:存儲與主題的狀態自恰的狀態。具體現察者角色實現抽象觀察者角色所要求的更新接口,以便使本身的狀態與主題的狀態相協調。如果需要,具體現察者角色可以保存一個指向具體主題對象的引用。具體觀察者角色通常用一個具體子類實現。

三、應用
在以下任一情況下可以使用觀察者模式:
1
、當一個抽象模型有兩個方面,其中一個方面依賴於另一方面,將這二者封裝在獨立的對象中以使它們可以各自獨立地改變和複用。
2
、當對一個對象的改變需要同時改變其它對象,而不知道具體有多少對象有待改變。
3
、當一個對象必須通知其它對象,而它又不能假定其它對象是誰。換言之,你不希望這些對象是緊密耦合的。

四、優缺點
Observer模式實現了表示層和數據邏輯層的分離,允許你獨立的改變目標和觀察者。你可以單獨複用目標對象而無需同時複用其觀察者, 反之亦然。它也使你可以在不改動目標和其他的觀察者的前提下增加觀察者。
下面是觀察者模式其它一些優缺點:
1
、目標和觀察者間的抽象耦合一個目標所知道的僅僅是它有一系列觀察者,每個都符合抽象的Observer類的簡單接口。目標不知道任何一個觀察者屬於哪一個具體的類。這樣目標和觀察者之間的耦合是抽象的和最小的。
因爲目標和觀察者不是緊密耦合的,它們可以屬於一個系統中的不同抽象層次。一個處於較低層次的目標對象可與一個處於較高層次的觀察者通信並通知它,這樣就保持了系統層次的完整。如果目標和觀察者混在一塊,那麼得到的對象要麼橫貫兩個層次(違反了層次性),要麼必須放在這兩層的某一層中(這可能會損害層次抽象)
2
、支持廣播通信不像通常的請求,目標發送的通知不需指定它的接收者。通知被自動廣播給所有已向該目標對象登記的有關對象。目標對象並不關心到底有多少對象對自己感興趣;
它唯一的責任就是通知它的各觀察者。這給了你在任何時刻增加和刪除觀察者的自由。處理還是忽略一個通知取決於觀察者。
3
、意外的更新因爲一個觀察者並不知道其它觀察者的存在,它可能對改變目標的最終代價一無所知。在目標上一個看似無害的的操作可能會引起一系列對觀察者以及依賴於這些觀察者的那些對象的更新。此外,如果依賴準則的定義或維護不當,常常會引起錯誤的更新,這種錯誤通常很難捕捉。
簡單的更新協議不提供具體細節說明目標中什麼被改變了,這就使得上述問題更加嚴重。如果沒有其他協議幫助觀察者發現什麼發生了改變,它們可能會被迫盡力減少改變。

五、舉例
Observer模式是一個應用比較廣泛的模式,在Java中,Observer模式的應用隨處可見(可參考筆記13中關於Java中CoR與Observer模式的對比部分內容),此外,JDK中特別提供了java.util.Observable類和Observer接口來方便程序員應用Observer模式,而C++方面,ATL則提供了IConnectionPointContainer、IConnectionPoint、IEnumConnectionPoints、IEnumConnections等以支持所謂的連接點及可連接對象等(相關示例見參考1),而在MFC中,一個文檔可以對應多個視圖,而當文檔發生更新後,可以通過UpdateAllViews來同步更新所有視圖,這雖然並非嚴格意義上的Observer模式的應用,但其本質是相似的。
前面曾經從Mediator模式的角度實現過一個ChatRoom的例子,這裏用Observer模式來解決同一問題:

#include <vector>
#include <iostream>
using namespace std;

class
 ChatRoom;
struct
 Observer
{

    virtual
 void SetChatRoom(ChatRoom* pChatRoom) = 0;
    virtual
 void Notify(const string& from, const string& to, const string& msg) = 0;
};


class
 ChatRoom    // Subject
{
private
:
    vector<Observer*> vo;
public
:
    virtual
 ~ChatRoom()
    {

        vector<Observer*>::iterator vi = vo.begin();
        for
 (; vi != vo.end(); vi++)
        {

            delete
 *vi;
        }
    }

    void
 Login(Observer* po)
    {

        vo.push_back(po);
        po->SetChatRoom(this);
    }

    void
 Logout(Observer* po)
    {

        vector<Observer*>::iterator vi = vo.begin();
        for
 (; vi != vo.end(); vi++)
        {

            if
 (*vi == po)
                vo.erase(vi);
        }
    }

    void
 Notify(const string& from, const string& to, const string& msg)
    {

        vector<Observer*>::iterator vi = vo.begin();
        for
 (; vi != vo.end(); vi++)
        {
            (*
vi)->Notify(from, to, msg);
        }
    }
};


class
 Chater : public Observer    // ConcreteObserver
{
private
:
    string name;
    ChatRoom* pChatRoom;
public
:
    Chater(const string& name) : name(name) {}
    void
 SetChatRoom(ChatRoom* pChatRoom)
    {

        this
->pChatRoom = pChatRoom;
    }

    void
 Send(const string& to, const string& msg)
    {

        pChatRoom->Notify(name, to, msg);
    }

    void
 Notify(const string& from, const string& to, const string& msg)
    {

        if
 (0 == to.compare(name))
        {

            cout << "[" << from.c_str() << "] to [" << to.c_str() << "]: "
                <<
 msg.c_str() << endl;
        }
    }
};


int
 main()
{

    ChatRoom cr;

    Chater* c1 = new Chater("David");
    Chater* c2 = new Chater("Jordan");
    Chater* c3 = new Chater("O'Neal");

    cr.Login(c1);
    cr.Login(c2);
    cr.Login(c3);

    c1->Send("Jordan", "You are a great basketball player");    // Send message to change subject state
    c2->Send("O'Neal", "Work hard, you are great!");

    return
 0;
}


在上述示例中,引起Subject(即ChatRoom)發生變化的是Observer(即Chater),而接收Subject發出的Notify消息的同樣是Observer,這在有些應用中可能是兩個/類不同的實體。此外,在上述示例中,當ChatRoom接收到消息時,不會對消息的內容進行解析,而是直接通過Notify通知所有Chater,由所有Chater自己負責檢查該消息是否是發送給自己的(實際的ChatRoom不可能採用這種形式),這在一定程度上與廣播非常相似,所以有時候,我們可以採用Observer模式來模擬軟件廣播。
以上示例中,Subject不對消息的內容進行檢查,而是盲目地進行廣播,在具體應用中,我們可以結合使用Mediator模式和Observer模式,將Subject設計成一個Mediator,對消息內容進行檢查,進而根據消息內容進行消息的Publish。

參考:
1
、http://www.codeproject.com/com/connectionpoint.asp
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章