觀察者模式 Observer Pattern

博客來源:http://blog.csdn.net/haimian520/article/details/51143513
之前總是想寫一個程序,大體上是這樣的:單服務器,多客戶端。服務器上比如說是個房屋價格信息,客戶端則是租房子,顯示價格。如果現在有特價,怎麼客戶端怎麼實時獲取?而且作爲客戶端,用戶定是不止一個,如何通知所有的客戶端呢?直到前天才發現,這是另一個模式—-觀察者模式中講解的 內容。在看這個之前已經心中有底了,客戶端數目是變化的,可以用向量vector;通知所有客戶端,for循環。

觀察者模式:

定義了對象之間的一對多依賴 ,這樣一來, 當一個對象改變狀態時,它的 所有依賴者 都會 收到通知自動更新

主題對象管理某些數據。
當主題內的數據改變,就會通知觀察者。
觀察者已經訂閱(註冊)主題以便在主題數據改變時能夠收到更新。

鴨子對象不是觀察者,鴨子對象過來告訴主題,它想當一個觀察者。
鴨子對象要註冊(訂閱)主題對象。
然後鴨子就已經是正式的觀察者了。 (此 鴨子 非 前面的策略模式裏面的鴨子)
主題有了新的數據值!
現在鴨子和其他所有觀察者都會收到通知 – 》 主題已經改變了。

定義觀察者模式:類圖

依賴:
主題具有狀態;觀察者使用這些狀態。
觀察者依賴主題來告訴他們狀態何時改變了(主題調用觀察者的方法。他們不在一個類中怎麼辦?把所有的觀察者對象存放到主題的變量中去,然後for循環調用觀察者對象的 update方法);
主題是真正擁有數據的人,觀察者是主題的依賴者,在數據變化時更新。
關於觀察者的一切,主題只知道觀察者實現了某個接口(也就是Observer接口)。主題不知道 也 不需要知道觀察者的具體類是誰、做了些什麼或其他任何細節。 – 》 鬆耦合

鬆耦合:當兩個對象之間鬆耦合,可以在不知道彼此間的細節的前提下,進行交互。

對於下面的 氣象監測應用

WeatherData這個主題(Subject)存儲溫度、溼度、氣壓等天氣狀況。當屬性發生改變時,會通知所有的顯示裝置(Observer觀察者)。

對於顯示裝置,可能會不同,比如:

每個佈告板都有差異,因此我們需要一個共同的接口。儘管佈告板的類都不一樣,但是它們都應該實現相同的接口,好讓WeatherData對象能夠知道如何把觀測值送給它們。所以每個應該有一個update方法,供WeatherData對象調用。

觀察者:

class Observer
{
public:
    virtual void update(float t, float p, float h ){}    
};
class CurrentObserver :public Observer, public Display
{
    float temperature;// 只對溫度感興趣
public:
    void update(float t, float p, float h){
        temperature = t;
    }
};

主題:

class Subject
{
public:
    // 主題保存一個觀察者列表,這樣可以通知所有的觀察者。
    // 但是呢,這個列表的元素怎麼來呢?通過註冊觀察者方法來添加        
    // 理所應當的是,該函數傳入的是一個觀察者對象。
    virtual void registerObserver(Observer *o){}
    virtual void removeObserver(Observer *o){}
    virtual void notifyObserver(){}
class WeatherData :public Subject
{
    floattemperature;          // 特定的Subject主題有溫度、溼度、壓強三個數據
    float humidity;
    float pressure; 

    vector <Observer*> observerLst;  // 存放所有的觀察者(指針)

    void registerObserver(Observer *o)
    {
        observerLst.push_back(o);
    }

    void removeObserver(Observer *o)   
    {   
        observerLst.erase(index of which element is equivalent to o);  
    } 

    void notifyObservers()        
    {  
        for each  observer.update(t, p, h);    
    };

};

然後呢,Subject類屬性改變時,需要通知所有觀察者。
改變屬性:

setValue(float t, float p , float h)
{ 
    temperature = t; 
    humidity = p; 
    pressure = h;          // 修改此類(WeatherData )的數據 
    notifyObservers();     // 通知所有觀察者 
}

對於觀察者怎麼寫呢?
首先觀察者每個的顯示方式不同,在前面有了個DisplayElement這個接口,封裝了不同的變化。在特定類型的觀察者需要實現DisplayElement裏面的display方法即可。

class Display
{
public:
    virtual void display(){};
};

所以:

class ConcreteObserver:public Observer , public DisplayElement 
{ 
    // 不的興趣 –》 只對溫度感興趣
    float temperature;
    // 不同的顯示方式 --> DisplayElement
    void display(){ /*...........*/  }
};

然後就有:

class ConcreteObserver:public Observer , public DisplayElement 
{ 
    ConcreteObserver() // 構造函數 
    { 
    }

    void update(/*有溫度,氣壓,溼度三個參數*/)
    {
        // 當Subject類的數據發生改變時,通知所有的觀察者更新數據
        // 有個小缺點:
        // Subject類將所有的數據都撂給了觀察者,而觀察者只挑揀他們感興趣的東西(有點浪費)
        // 比如:ConcreteObserver這個類只對 溫度 感興趣,如下:

        this-> temprature = t;      // 該類有一個屬性float temperature;

        // 而這個update函數因爲要統一函數簽名,讓所有的觀察者都有相同的函數。
        // 以便在Subject中以相同的方式傳出所有數據,所以: 
        // void update(float temp, float pressure, float humidity) 
        // 也就是Observer中update那樣寫的原因

        // 該類數據更新了,爲了能夠看得到,或者說是即時顯示,則需要在這裏調用顯示方法:
        display();      // 顯示一下最新數據
    }   

    void display(){ /*.........*/ }    // 實現DisplayElement接口的display方法。
};

對於構造函數: ConcreteObserver()傳入的參數又是Subject *weatherData。
在註冊(訂閱)主題時:

weatherData->registerObserver(this);

this是指它本身。在創建對象時,它將類本身ConcreteObserver創建的對象傳給了Subject類型的WeatherData了的對象了。

如何將氣象觀測值放到佈告板上?
在觀察者模式中,如果我們把WeatherData對象當作主題,把佈告板當作觀察者,佈告板爲了取得信息,就必須先向WeatherData對象註冊。

class CurrentObserver : public Observer
{
private:
    Subject *weatherData;
public:
    CurrentObserver()
    {
        weatherData->registerObserver(this);
    }
}

你會發現上面的CurrentObserver()構造函數並不能滿足需要:
因爲首先weatherData是個指針,要指向一個確切的東西。不然weatherData 是個 空指針,怎麼對它進行操作呢。
在下面的main函數中,創建了一個WeatherData對象,將其地址傳給了CurrentObserver

WeatherData *wd = new WeatherData();    
CurrentObserver *fo = new CurrentObserver(wd);

相應的構造函數改爲:

CurrentObserver(Subject *weatherData) // 使用基類的Subject類型指針,適用於所有主題Subject 
{
    temperature = 0.0f;
    weatherData->registerObserver(this); // 構造函數,在這裏調用Subject類的registerObserver方法,
                                         // 將該類的實例加入Subject類的List中去。
}

不知道你們看懂了沒?反正我不知道我在說啥。

C++代碼:

    #include<iostream>
    #include<vector>

    using namespace std;

    class Display
    {
    public:
        virtual void display(){};
    };

    class Observer
    {
    public:
        virtual void update(float t, float p, float h ){}    
    };


    class Subject
    {
    public:
        virtual void registerObserver(Observer *o){}
        virtual void removeObserver(Observer *o){}
        virtual void notifyObserver(){}
    };

    // ConcreteSubject 
    class WeatherData :public Subject
    {
    private:
        float temp;
        float pressure;
        float hum;
        vector <Observer*> observerLst;
    public:
        WeatherData()
        {
            temp = 0.0f;
            pressure = 0.0f;
            hum = 0.0f;
        }

        void registerObserver(Observer *o)
        {
            observerLst.push_back(o);
        }
        void removeObserver(Observer *o)
        {

            for (int i = 0; i < int(observerLst.size()); ++i)
            {
                if (o == observerLst[i])
                {
                    observerLst.erase(observerLst.begin() + i);
                    break;
                }
            }
        }
        void setValue(float t, float p, float h)
        {
            temp = t;
            pressure = p;
            hum = h;

            notifyObserver();
        }
        void notifyObserver()
        {
            for (int i = 0; i < int(observerLst.size()); ++i)
            {
                (*observerLst[i]).update(temp, pressure,hum);
            }
        }
    };

    // ConcreteObserver
    class CurrentObserver :public Observer, public Display
    {
    private:
        float temperature;
        Subject *weatherData;
    public:
        CurrentObserver(Subject *weatherData)
        {
            //temperature = 0.0f;
            this->weatherData = weatherData;            
            weatherData->registerObserver(this);
            weatherData->notifyObserver();
        }
        void update(float t, float p, float h){
            temperature = t;
            display();
        }
        void display()
        {
            cout << "CurrentObserver: "
                << "temperature  : " << temperature 
                << endl;
        }
    };

    int main()
    {
        WeatherData *wd = new WeatherData();

        CurrentObserver *fo = new CurrentObserver(wd);

        cout << "fo->display(): ";
        fo->display();

        wd->setValue(10, 10, 10);

        return 0;
    }

設計原則:爲了交互對象之間的鬆耦合設計而努力。

鬆耦合的設計之所以能讓我們建立有彈性的OO系統,能夠應對變化,是因爲對象之間的互相依賴降到了最低。

The observer pattern is a software design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods. It is mainly used to implement distributed event handling systems. The Observer pattern is also a key part in the familiar model–view–controller (MVC) architectural pattern.[1] The observer pattern is implemented in numerous programming libraries and systems, including almost all GUI toolkits.

觀察者模式 : Subject 維護一個觀察者依賴列表。如果Subject的狀態發生改變並自動的通知他們,通常通過調用觀察者的方法。主要用在實現分佈式事件處理系統。觀察者模式也是大家熟悉的模型 - 視圖 - 控制器(MVC)架構模式的一個關鍵組成部分。觀察者模式在編程庫和系統中有大量的實現,包括幾乎所有的GUI工具。

We can not talk about Object Oriented Programming without considering the state of the objects. After all object oriented programming is about objects and their interaction. The cases when certain objects need to be informed about the changes occured in other objects are frequent. To have a good design means to decouple(儘可能的去耦合) as much as possible and to reduce the dependencies(降低依賴). The Observer Design Pattern can be used whenever a subject has to be observed by one or more observers.
當另一個對象頻繁的發生變化時,該對象需要被頻繁通知。 觀察者模式可以被用在 主題 被一個或者多個 觀察者 觀察的任何情況。

這裏寫圖片描述
圖中:實線箭頭 依賴; 虛線箭頭 實現;
ConcreteSubject維護一個觀察者列表;
ConcreteObserver的構造函數中

Subject *weatherData;
CurrentObserver(Subject *weatherData)
{
    this->weatherData = weatherData;
    weatherData->registerObserver(this);
}

還有一個網站:http://gameprogrammingpatterns.com/observer.html
很好的網站,講了一下引擎實現的原型。

它對Observer Pattern進行分析:
太慢?不是的,它並不是事件訂閱,消息機制或者數據綁定,發送數據僅僅是遍歷一下列表,然後調用一些虛方法。

太快?不是的,慢的觀察者會阻塞Subject的執行。如UI編程,解決方法:放到另一個線程當中或者一個工作隊列中(queue)。

the observer registers with the subject, expressing its interest in observing. When a state change occurs, the subject notifies the observer of the change. When the observer no longer wishes to observe the subject, the observer unregisters from the subject. These steps are known as observer registration, notification, and unregistration, respectively.

  1. Observer Registration:

    observer invokes the Register method on the subject, passing itself as an argument.
    Once the subject receives this reference, it must store it in order to notify the observer when a state change occurs sometime in the future. Rather than storing the observer reference in an instance variable directly, most observer implementations delegate this responsibility to a separate object, typically a container. Use of a container to store observer instances provides important benefits that we will discuss shortly. With that in mind, the next action in the sequence is the storage of the observer reference denoted by the invocation of the Add method on the container.
    UML sequence diagrams:
    這裏寫圖片描述

    再來一個:
    這裏寫圖片描述
    (Basic Observer interaction)

  2. Observer Notification:

    When a state change occurs (AskPriceChanged), the subject retrieves all the observers within the container by invoking the GetObservers method. The subject then enumerates through the retrieved observers, calling the Notify method, which notifies the observer of the state change.
    這裏寫圖片描述

在前面瞭解到:當Subject對象發生變化是,Subject會把所有的信息都撂給所有的Observer,有Observer來決定挑選哪些信息。這就是Push model;這會導致傳送信息量過大,而且在傳送的時候,決定傳送哪些信息會出錯。
Push model

In the push model, the client sends all relevant information regarding the state change to the subject, which passes the information on to each observer. If the information is passed in a neutral format (for example, XML), this model keeps the dependent observers from having to access the client directly for more information. On the other hand, the subject has to make some assumptions about which information is relevant to the observers. If a new observer is added, the subject may have to publish additional information required by that observer. This would make the subject and the client once again dependent on the observers - the problem you were trying to solve in the first place. So when using the push model, you should err on the side of inclusion when determining the amount of information to pass to the observers. In many cases, you would include a reference to the subject in the call to the observer. The observers can use that reference to obtain state information.
還有一種Pull model
In the pull model, the client notifies the subject of a state change. After the observers receive notification, they access the subject or the client for additional data (see Figure 5) by using a getState() method. This model does not require the subject to pass any information along with the update() method, but it may require that the observer call getState() just to figure out that the state change was not relevant. As a result, this model can be a little more inefficient(但是低效).

Another possible complication occurs when the observer and the subject run in different threads (for example, if you use RMI to notify the observers). In this scenario, the internal state of the subject may have changed again by the time the observer obtains the state information through the callback. This may cause the observer to skip an operation.不懂
這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章