1. 程式人生 > >模板應用--觀察者模式

模板應用--觀察者模式

介紹
    寫不出介紹來,直接搜尋,來自百度百科:觀察者模式(有時又被稱為釋出-訂閱Subscribe>模式、模型-檢視View>模式、源-收聽者Listener>模式或從屬者模式)是軟體設計模式的一種。在此種模式中,一個目標物件(Subject)管理所有相依於它的觀察者物件(Observer),並且在它本身的狀態改變時主動發出通知。這通常透過呼叫各觀察者所提供的方法來實現。此種模式通常被用來實作事件處理系統。
    舉個例子,使用者介面上有柱狀圖、餅狀圖、歷史記錄表,他們都作為一個觀察者,業務資料是被觀察者(Subject),各顯示區域觀察業務資料的變化,發現數據變化後,就更新介面顯示。面向物件設計的一個原則是:系統中的每個類將重點放在某一個功能上,而不是其他方面。一個物件只做一件事情,並且將他做好。觀察者模式在模組之間劃定了清晰的界限,提高了應用程式的可維護性和重用性。


背景
    在我看來,面向物件程式設計方法主要目的就是解耦使其可維護、易擴充套件、可複用等,通過封裝、繼承、多型來實現,設計模式談論更多的也是以組合、聚合或關聯的解耦方式來達到這些目的,所以通常來說,程式碼的質量是高耦合<低耦合,低耦合<無耦合。
    關於觀察者模式,網上有鋪天蓋地的原理分析、應用場景介紹、例項程式碼,不過大致都是一些基本思想,包括例項程式碼也僅僅是作為入門參考,具體應用到專案中還需考慮很多細節問題,往往這些細節問題決定了這個程式碼框架的可用性和可擴充套件性的程度。這其中就包括以下必須仔細思考的問題:
    1.一個Observer可否觀察多個Subject?(別拿“一個類做一件事”來說事)
    2.對Subject進行更新時,可否選擇隨意的方式?比如更新一小部分和更新全部
    3.Observer收到資料通知時,可否有選擇地感知/忽略資料更新發起者?可否感知/忽略資料更新前後的狀態?
    4.抽象要到哪一級別?或者說哪些介面必須抽象出來?引數有哪些?
    5.如何界定達到了最低耦合度?是否可以做到“無耦合”?
    6.如何保證參與物件的生命週期?
    7.如何做到執行緒安全?線上程安全的前提下又如何保證不會出現競態死鎖?
    。。。。。。


實現
    針對上面提出的問題,一個一個來解決。
    1.一個Observer可否觀察多個Subject?

    如果抽象出Observer和Subject作為基類,Subject要提供的外部介面包括update、register、unregister,內部介面有notify進行事件通知;Observer要提供data_changed介面供事件通知呼叫,為了讓Observer可否觀察多個具體的Subject物件,讓所有資料類繼承自Subject基類,然後這個Observer物件逐一呼叫各具體Subject的register介面註冊自己成為觀察者。貌似能解決第一個問題,可仔細想想,Observer只有一個data_changed介面,也就是說ConcreteObserver的data_changed實現只能是下面這個樣子的:

ConcreteObserver::data_changed(Subject* dt, Event evt){
	switch (dt->type()){
	    case type_a:	// subject a changed
		break;
	    case type_b:	// subject b changed
		break;
	    default:		// others
		break;
	}
    }

以後增加Subject的話,還得修改這個data_changed實現,而且必須有一種辦法能識別Subject的具體型別,因為Subject是基類。這種見得最多的方式要解決這第一個問題顯得很無力,即使最終實現了,也會很麻煩。
    為了能夠讓Observer觀察多個具體的Subject物件,必須再進一步解耦:第一,作為Observer不能依賴一個統一的data_changed介面,應該貓住貓窩,狗住狗窩,不能所有Subject有事件通知都呼叫統一的data_changed介面,這就決定了不能抽象出Observer;第二,既然沒有抽象的Observer,那麼Subject必須得知道事件通知時應該呼叫各Observer的哪個介面(Observer的data_changed介面有多個)。如果技術上能做到,貌似這是理想狀態。


    2.對Subject進行更新時,可否選擇隨意的方式?比如更新一小部分和更新全部。
    可對Subject資料進行隨意方式的更新,也就意味著Subject的update介面只有一種的話是無法完成的,那具體是多少種也是不得而知的,結合前面第一個問題,由此很容易就能想到,只能使用“範化”的方式來實現Subject,這樣,不管Observer是什麼型別、資料更新的update方式有多少種,都是能辦到的,當然,為了update方式有多種,也不是非得範化Subject,還可以把update這個操作抽象出來來達到此目的。


    3.Observer收到資料通知時,可否有選擇地感知/忽略資料更新發起者?可否感知/忽略資料更新前後的狀態?
    如果Subject的資料更新發起者(叫做sender好了)更新資料的同時告知Subject發起者自身的資訊,並且順帶將更新前後狀態也一併傳入Subject的notify介面,如考慮問題1時列出的data_changed介面附帶的引數Event,然而這樣一來就必須讓Subject範化了,因為Event這個型別是不固定的,所以才能實現針對不同Subject進行“有選擇地”感知/忽略,否則,把Event再抽象出來的話,複雜度又上升了,如又要維護一個繼承鏈、又要決定抽象出哪些Event介面。。。
    
    4.抽象要到哪一級別?或者說哪些介面必須抽象出來?引數有哪些?
    5.如何界定達到了最低耦合度?是否可以做到“無耦合”?
    分析到目前為止,幾乎就可以肯定,不使用多型的方式而改用範型來實現此模式,固然不存在抽象這個問題了。

    要解決6和7兩個問題,還為之過早,先把大致框架寫出來,再考慮執行緒安全的問題吧。

#include <map>
#include <boost/function.hpp>
#include <boost/noncopyable.hpp>

//////////////////////////////////////////////////////////////////////////

template<class updater, class Data>
struct subject_update_evt 
{
	updater		sender_;
	Data		old_data_;
	Data		new_data_;
};

template <class Data, class Event>
class subject : public boost::noncopyable
{
public:
	typedef boost::function<void (Event evt)>		observer_obj;
	typedef Data						data_type;
	typedef Event						evt_type;
	typedef	std::map<void*, observer_obj>			observer_objs;
	typedef subject<Data, Event>				this_type;
	typedef boost::function<bool (data_type&, evt_type*)>	special_updater;
	
protected:
	/* 
	** the original data that need to be monitored
	*/
	data_type		data_;
	/* 
	** hold all of the observers that interest in data
	*/
	observer_objs	observers_;
	//!
	bool			changing_;

	/* 
	** notify all observers that the data has changed
	*/
	void		notify(observer_objs& objs, Event evt)
	{
		for(observer_objs::iterator it = objs.begin(); it != objs.end(); ++it){
			try{
				it->second(evt);
			}catch(...){}
		}
	}

public:
	/* 
	** standard constructor, must be hidden from outside
	*/
	subject()
		:changing_(false){}

	/* 
	** return the const reference of original data type, it cannot be modified outside
	*/
	const typename this_type::data_type&	get_data(){return data_;}

	operator Data() const {return data_;}

	/* 
	** register a observer
	** the parameter 'obs' must be a functor that can call observer's data_changed function
	*/
	bool			register_obs(void* ptr, const typename this_type::observer_obj& obs)
	{
		if (changing_)
			return false;
		observer_objs::iterator it = observers_.find(ptr);
		if (it != observers_.end())
			return false;

		observers_[ptr] = obs;
		return true;
	}

	/* 
	** unregister a observer
	** the parameter 'ptr' is the data_changed receiver object that registered.
	*/
	bool		unregister_obs(void* ptr)
	{
		if (changing_)
			return false;
		observer_objs::iterator it = observers_.find(ptr);
		if (it != observers_.end()){
			observers_.erase(it);
			return true;
		}
		return false;
	}
	
	/* 
	** full update the data and notify observers
	*/
	void		update(const Data& data, Event evt)
	{
		if (changing_)
			return;
		changing_ = true;
		data_ = data;
		notify(observers_, evt);
		changing_ = false;
	}

	/* 
	** customize update method and notify observers after that
	*/
	void		update(special_updater op)
	{
		if (changing_)
			return;
		Event evt;
		if (op(data_, &evt)){
			changing_ = true;
			notify(observers_, evt);
			changing_ = false;
		}
	}
};

由前面初步得出封裝的模板類Subject類如下:
    Subject有兩個模板引數:Data和Event。Data就是真正被觀察的資料型別,可以是任何型別如int、long、double等POD型別,也可以是某個STL集合類或特定的類;Event是執行更新操作後通知Observer的附帶引數型別,在此將它範化的好處顯而易見,Event可以是任何型別,根據特定需要對它進行任意擴充套件。
    這裡Observer不是一個抽象基類指標或引用,而是boost::function物件例項取代,以此規避面向物件(具體是多型)可能帶來的問題:第一,Subject<T>類只認抽象基類Observer的話,直接決定了了Observer類有幾個data_changed介面(通常就一個),而且介面引數還是固定的,這樣,一個Observer就只能關注一個Subject物件了,儘管抽象出Observer基類進行解耦,但還是型別依賴,而通過boost::function可以解除他們之間的耦合關係,即“無耦”,而且引數還可以是任意型別、數量;第二,需函式可能帶來的二進位制相容性問題(具體網上一大把好的文章),哪天要對Observer進行擴充套件,必須小心這個陷阱,boost::function則沒有這方面的問題,所以理想情況下,這個Observer可以是不帶需函式的,同樣達到面向物件的目的,且更為靈活。
    資料訪問介面get_data和型別Data的cast過載,他們都返回Subject持有的Data型別物件。
    註冊、反註冊函式register_obs、unregister_obs,註冊時,必須提供一個key,作為unregister_obs時的依據,因為boost::function物件不支援比較器,unregister_obs不知道到底是要反註冊哪個。
    兩個update介面,一個用來full update即全部替換;一個用來special update,具體的更新動作由指定的special_updater型別引數決定,special_updater是boost::function<bool (data_type&, evt_type*)>型別的typedef,該functor接受兩個引數,一個是原始資料的引用型別,一個是需傳回的Event*型別,functor被呼叫時可對data做任意修改,然後通過Event物件告知修改部分、原始部分等等任何額外的資訊,更新完後通知所有Observer。
    notify介面,這沒什麼可說的了,就是遍歷持有的Observer進行呼叫而已。
    關於這兩個update介面要特別提一下,它可能存在一個潛在的問題。假設有這樣一種場景,在Observer被呼叫通知資料被更改且尚未返回時,如果它又引起了Subject<T>持有的Observers集合被更改,那麼將會引發異常:迭代器失效,為避免這個情況,必須加上一個狀態變量表示當前是否處於通知狀態,以便在Observers容器被更改前有個判斷依據。
    凡是涉及到狀態變數的區域性切換(我理解為在一個函式呼叫內進行切換)的問題,不可避免的都可能出現這樣的情況:如果狀態切換之間出現丟擲異常,那麼很可能這個狀態不能恢復到之前的狀態,如這裡的update函式被呼叫時,notify丟擲異常,那麼changing_變數將沒有機會被改回原來的狀態,這時需借用解構函式自動呼叫的機制來完善,如經常能見到的執行緒同步使用的各種Lock類,在這模仿出一個scope_state_reverse類來完成這個事情,它的實現大致如下:
struct scope_state_reverse{
		bool&	state_;
		scope_state_reverse(bool& state)
			:state_(state){
			assert(!state_);
			state_ = !state_;
		}
		~scope_state_reverse(){
			state_ = !state_;
		}
	};

然後update介面將變成:
bool	update(const Data& data, Event evt)
	{
		if (changing_)
			return false;

		scope_state_reverse ssr(changing_);
		data_ = data;
		notify(observers_, evt);
		return true;
	}

 6.如何保證參與物件的生命週期?
    7.如何做到執行緒安全?線上程安全的前提下又如何保證不會出現競態死鎖?
    到這該考慮執行緒安全的問題了。
    Subject類成員在多執行緒中都需要執行緒被同步處理,給公共介面內都加上鎖吧,如update、register_obs、unregister_obs,使用boost::recursive_mutex,再配合boost::unique_lock模板類達到執行緒同步處理。boost提供了多種的mutex,這裡只能使用recursive_mutex這種,考慮到同一個執行緒內可能多次進行加鎖,這會block掉整個執行緒,而且其他執行緒再次加鎖時也被通通block,比如update被呼叫時,Observer呼叫通知返回前又呼叫了註冊、反註冊函式。
    既然資料成員的訪問都加上鎖了,那麼get_data函式與Data型別cast自動轉換函式這麼裸露也就不安全了,返回資料之前也必須加鎖:
bool	get_data(this_type::data_type& dt)
	{
		boost::unique_lock<mutex> guard(mtx_);
		if (!guard)
			return false;

		dt = data_;
		return true;
	}

如果Data型別是個大型集合或者是需要特定的訪問方式,直接dt = data_;的方式顯得受眾過窄,也可以向過載update函式那樣通過提供一個特定的訪問functor,再把data_傳給它,讓它自己去根據需要進行訪問就行了,如:
template<class accessor>
	void		accessed_by(accessor acc)
	{
		boost::unique_lock<mutex> guard(mtx_);
		if (!guard)
			return;

		const typename this_type::data_type& replacement = data_;
		acc(replacement);
	}

物件生命週期的問題。這裡的參與者有兩類,Subject與Observer。Observer呼叫Subject的任何函式只能通過boost::weak_ptr<Subject<T> >提升為boost::shared_ptr後呼叫,具體原因下次補上,還有test case和使用案例
    修改完後,程式碼如下:
#include <map>
#include <boost/function.hpp>
#include <boost/noncopyable.hpp>
#include <boost/foreach.hpp>
#include <boost/thread.hpp>

//////////////////////////////////////////////////////////////////////////

template<class updater, class Data>
struct subject_update_evt 
{
	updater		sender_;
	Data		old_data_;
	Data		new_data_;
};

template <class Data, class Event>
class subject : public boost::noncopyable
{
public:
	typedef boost::function<void (Event evt)>	observer_obj;
	typedef Data								data_type;
	typedef Event								evt_type;
	typedef	std::map<int, observer_obj>			observer_objs;
	typedef subject<Data, Event>				this_type;
	typedef boost::function<bool (data_type&, evt_type*)>	special_updater;
	typedef boost::recursive_mutex				mutex;

protected:
	/* 
	** the original data that need to be monitored
	*/
	data_type		data_;
	/* 
	** hold all of the observers that interest in data
	*/
	observer_objs		observers_;
	//! 
	mutex			mtx_;
	//!
	bool			changing_;

	struct scope_state_reverse{
		bool&	state_;
		scope_state_reverse(bool& state)
			:state_(state){
			assert(!state_);
			state_ = !state_;
		}
		~scope_state_reverse(){
			state_ = !state_;
		}
	};

	/* 
	** notify all observers that the data has changed
	*/
	void		notify(observer_objs& objs, Event evt)
	{
		for(observer_objs::iterator it = objs.begin(); it != objs.end(); ++it){
			try{
				it->second(evt);
			}catch(...){}
		}
	}

private:
	/* 
	** standard constructor, must be hidden from outside
	*/
	subject()
		:changing_(false){}

public:
	boost::shared_ptr<this_type> Create()
	{
		return new boost::shared_ptr<this_type>(this_type);
	}
	/*
	** get the data (thread safe)
	** this method will make a copy of source data and reset to parameter dt
	** return true if the parameter dt is reset
	*/
	bool		get_data(this_type::data_type& dt)
	{
		boost::unique_lock<mutex> guard(mtx_);
		if (!guard)
			return false;

		dt = data_;
		return true;
	}

	/*
	** access the data (thread safe)
	** this method only pass the source data to the accessor functor.
	** the accessor functor must has the signature of accepting a Data type parameter
	*/
	template<class accessor>
	void		accessed_by(accessor acc)
	{
		boost::unique_lock<mutex> guard(mtx_);
		if (!guard)
			return;

		const typename this_type::data_type& replacement = data_;
		acc(replacement);
	}

	/* 
	** register a observer
	** the parameter 'obs' must be a functor that can call observer's data_changed function
	** return true means register successfully
	*/
	bool			register_obs(void* ptr, const typename this_type::observer_obj& obs)
	{
		// maybe block here
		boost::unique_lock<mutex> guard(mtx_);
		if (guard){
			if (changing_)
				return false;
			observer_objs::iterator it = observers_.find(ptr);
			if (it == observers_.end()){
				observers_[ptr] = obs;
				return true;
			}
		}
		return false;
	}

	/* 
	** unregister a observer
	** the parameter 'ptr' is the data_changed receiver object that registered.
	*/
	bool		unregister_obs(int key)
	{
		// maybe block here
		boost::unique_lock<mutex> guard(mtx_);
		if (guard){
			if (changing_)
				return false;
			observer_objs::iterator it = observers_.find(key);
			if (it != observers_.end()){
				observers_.erase(it);
				return true;
			}
		}
		return false;
	}
	
	/* 
	** full update the data and notify observers
	*/
	bool		update(const Data& data, Event evt)
	{
		// maybe block here
		boost::unique_lock<mutex> guard(mtx_);
		if (!guard || changing_)
			return false;

		scope_state_reverse ssr(changing_);
		data_ = data;
		notify(observers_, evt);
		return true;
	}

	/* 
	** customize update method and notify observers after that
	*/
	bool		update(special_updater op)
	{
		// maybe block here
		boost::unique_lock<mutex> guard(mtx_);
		if (!guard || changing_)
			return false;

		scope_state_reverse ssr(changing_);
		Event evt;
		if (op(data_, &evt)){
			notify(observers_, evt);
			return true;
		}
		return false;
	}
};