深入淺出設計模式C++實現(2)——觀察者模式
來自專欄深入淺出設計模式——C++實現
觀察者模式
相比於策略模式,觀察者模式可以複雜很多。用可以是因為也能寫的很簡單,就像我接下來要展示的代碼一樣。複雜是因為考慮到多個觀察者和多個觀測對象以及多台主機的情形。
按照書上的理解,觀察者模式可以簡化成報紙訂閱系統。報紙會給所有訂閱的用戶發送新報紙,任何人都可以成為訂閱用戶或是取消訂閱。
從最簡單的角度來考慮,需要有兩種類型,一種是觀測對象,一種是觀察者。觀測對象內部存有一定量的觀察者,它會定期將消息發送給這些觀察者,同時可以增刪這些觀察者。觀察者則沒有太多特點。
消息的發送這個用法有誤解性,並非要求二者之間建立一個類似socket的機制,這太過複雜了,並且大材小用。觀測對象內部是有數據的,觀察者只要有一個接收這些數據為參數的介面,觀測對象就能通過這個介面發送消息。觀察者是知道觀測對象有何種類型的數據的,並且有自己獨特的利用方式,所以這個介面是像rabbit和tank一樣best match的。
當然,光光觀察者抽象出介面是不夠的,觀測對象最好也抽象出一個介面,將觀察者管理的內容放到介面中。這樣就會給人一種泛用性很強的錯覺。
不過,如果觀測對象總是將全部數據推送給觀察者,觀察者對數據的利用率會很低。然而在這個設計中,觀測對象對觀察者的內部是不知情的,只知道對方實現了觀察者介面還註冊成為了自己的訂閱用戶。所以觀測對象很難做到推送合適的數據給觀察者。實在要做其實也可以,只要在觀察者介面里添加觀察的是哪幾項參數就可以了。但我感覺吧,這樣觀察者介面就變得不優雅了,太過臃腫,和具體實現綁定,而且傳遞參數的格式也是個問題。所以就衍生出一種新的傳遞方式,與推相反的拉。推是觀測對象將數據發給觀察者,拉是觀察者將數據從觀測對象處取出。這樣設計使得取數據的部分放到觀察者的具體實現中,介面功能簡化。
代碼
代碼地址 這裡代碼實現的同樣是書中的氣象站功能,採用拉的模式,含蓄地說具有很大的提升空間。
class Observer{public: virtual void update() = 0;};class Observable{public: virtual void addObserver(Observer&) = 0; virtual void deleteObserver(Observer&) = 0; virtual void notifyObservers() = 0;};
這是最基本的Observer和Observable介面。觀察者實現前一個,觀測對象實現後一個。update()方法就是觀測對象提醒觀察者來拉數據了。add,delete,notify則功能很明顯了。
class WeatherData :public Observable{private: set<Observer*> observers; float temperature,humidity,pressure; bool changed = false;public: void addObserver(Observer& o){ observers.insert(&o);} void deleteObserver(Observer& o){ observers.erase(&o);} void notifyObservers() { if (changed){ for (auto o = observers.begin(); o != observers.end(); o++){ (**o).update(); } } } void measurementsChanged() { setChanged(); notifyObservers(); } void setMeasurements(float temperature, float humidity, float pressure) { this->temperature = temperature; this->humidity = humidity; this->pressure = pressure; measurementsChanged(); } void setChanged() { changed = true; } float getTemperature(); float getHumidity(); float getPressure();};
這是具體的氣象站類。有一大堆額外的東西。首先是一個存儲所有觀察者指針的set。為什麼這裡用指針呢。如果直接用Observer對象的話,在加入set過程中會調用拷貝構造函數複製一份,這樣在程序運行過程中就會存在多個觀察者副本。當然如果像是例子里這樣簡單就沒問題,如果複雜一點,含有一些屬性,很容易就會數據不同步,運行出錯。別的也沒什麼了。changed對象用於記錄是否需要更改,在setChanged方法中,可以設計額外的功能,比如固定時刻才會通知之類的。通過這個影響notify功能。 方法的調用順序為setMeasurements,measurementsChanged,setChanged,notifyObservers.
除了這個好像也沒有什麼重要部分了,收工。
ciao
最好別看
這個設計可以說還有很多要改的地方。比如說一個觀察者如何觀測多個觀測對象。如果都通過同一個update()介面的話,如何去分辨是哪個觀測對象需要更新。這可能就需要RTTI的部分了。由於沒有相關經驗也就不寫了。觀測對象的管理同樣很麻煩,啊,總之為了完善功能就會越來越麻煩。最後發現拆成介面寫的意義不是很大,除了復用了一部分代碼。要將多種觀察者、觀測對象混合在一起的話還需要更多的工作。
推薦閱讀:
TAG:設計模式 |