模板应用--观察者模式

介绍
    写不出介绍来,直接搜索,来自百度百科:观察者模式(有时又被称为发布-订阅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;
	}
};



  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值