觀察者模式(Observer Pattern)
定義了對象之間的一對多依賴,這樣一來,當一個對象改變狀態時,它的所有依賴者都會受到通知並且自動更新。並且有“推”和“拉”兩種通知方式。
設計原則:為了互動對象之間的松耦合設計而努力
問題:
現在需要建立一個Internet 氣象站,WeatherData 對象可以追蹤目前的天氣狀況,現在有三個布告板,分別顯示目前的狀況,氣象統計以及簡單的預報。當WeatherData擷取了最新的資料的時候,三種布告板必須即時更新。
下面來看一個例子:
鴨子物件過來告訴主題,它想當一個觀察者
鴨子物件現在已經成為正式觀察者
主題有了新資料,現在鴨子可以收到資料
老鼠對象要求從觀察者中把自己除名
老鼠離開了
主題有了新資料,老鼠接收不到
定義觀察者模式的類圖:
#include<iostream>#include<list>using namespace std;class Observer{public:virtual void update(float temp,float humidity, float pressure) = 0;};class Subject{public:virtual void registerObserver(Observer *o) = 0;virtual void removeObserver(Observer *o) = 0;virtual void notifyObservers() = 0;};class DisplayElement{public:virtual void display() = 0;};class WeatherData: public Subject{public:WeatherData(){observers.clear();}void registerObserver(Observer *o){observers.push_back(o);}void removeObserver(Observer *o){observers.remove(o);}void notifyObservers(){for( list<Observer *>::iterator it = observers.begin(); it != observers.end(); ++it){(*it)->update(temperature,humidity,pressure);}}void measurementsChanged(){notifyObservers();}void setMeasurements(float temperature, float humidity,float pressure){this->temperature = temperature;this->humidity = humidity;this->pressure = pressure;//可以根據擷取的資料來判斷要不要設定measurementsChanged();}private:list<Observer*> observers;float temperature;float humidity;float pressure;};class CurrentConditionsDisplay: public Observer,public DisplayElement{public:CurrentConditionsDisplay(Subject * weatherData){this->weatherData = weatherData;//註冊成為觀察者this->weatherData->registerObserver(this);}~CurrentConditionsDisplay(){}void update(float temperature,float humidity,float pressure){this->temperature = temperature;this->humidity = humidity;//繼承的方法被重寫display();}void display(){cout << "Current conditions:" << temperature << "F degrees and" << humidity << "%humidity" << endl;}private:float temperature;float humidity;Subject * weatherData;};int main(){WeatherData *weatherData = new WeatherData();CurrentConditionsDisplay *currentDisplay = new CurrentConditionsDisplay(weatherData);//可以自己定義其他的display 類型//StatisticDisplay *statisticsDisplay = new StatisticDisplay(weatherData);weatherData->setMeasurements(80,65,30.4f);weatherData->setMeasurements(82,70,29.2f);delete weatherData;delete currentDisplay;}
上述的設計充分體現了松耦合的原則。當兩個對象之間松耦合,他們依然可以互動,但是不太清楚彼此的細節。觀察者模式提供了一種對象設計,讓主題和觀察者之間松耦合。關於觀察者的一切,主題只知道觀察者實現了某個介面(Observer 純虛類),並不需要知道觀察者的具體類是誰、做了些什麼或其他細節。任何時候我們可以增加新的觀察者。因為主題唯一依賴的東西是一個實現Observer介面的對象列表,所以我們可以隨時增加觀察者。
假如我們有個新的具體類需要當觀察者,我們不需要為了相容新類型而修改主題的代碼,所要做的就是在新的類裡實現此觀察者的介面,然後註冊觀察者即可。
上述的設計其實還存在一些問題,當資料改變的時候,主題“推”所有資料給觀察者,即使這些資料不是必須的(上例中的pressure資料)。觀察者模式還有另外一種思路,觀察者提供getter 方法,當需要時,觀察者來“拉”走自己所需要的資料。HeadFirst 上面是利用Java提供的介面實現的。