介绍
写不出介绍来,直接搜索,来自百度百科:观察者模式(有时又被称为发布-订阅Subscribe>模式、模型-视图View>模式、源-收听者Listener>模式或从属者模式)是软件设计模式的一种。在此种模式中,一个目标物件(Subject)管理所有相依于它的观察者物件(Observer),并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实作事件处理系统。
举个例子,用户界面上有柱状图、饼状图、历史记录表,他们都作为一个观察者,业务数据是被观察者(Subject),各显示区域观察业务数据的变化,发现数据变化后,就更新界面显示。面向对象设计的一个原则是:系统中的每个类将重点放在某一个功能上,而不是其他方面。一个对象只做一件事情,并且将他做好。观察者模式在模块之间划定了清晰的界限,提高了应用程序的可维护性和重用性。
背景
在我看来,面向对象编程方法主要目的就是解耦使其可维护、易扩展、可复用等,通过封装、继承、多态来实现,设计模式谈论更多的也是以组合、聚合或关联的解耦方式来达到这些目的,所以通常来说,代码的质量是高耦合<低耦合,低耦合<无耦合。
关于观察者模式,网上有铺天盖地的原理分析、应用场景介绍、实例代码,不过大致都是一些基本思想,包括实例代码也仅仅是作为入门参考,具体应用到项目中还需考虑很多细节问题,往往这些细节问题决定了这个代码框架的可用性和可扩展性的程度。这其中就包括以下必须仔细思考的问题:
1.一个Observer可否观察多个Subject?(别拿“一个类做一件事”来说事)
2.对Subject进行更新时,可否选择随意的方式?比如更新一小部分和更新全部
3.Observer收到数据通知时,可否有选择地感知/忽略数据更新发起者?可否感知/忽略数据更新前后的状态?
4.抽象要到哪一级别?或者说哪些接口必须抽象出来?参数有哪些?
5.如何界定达到了最低耦合度?是否可以做到“无耦合”?
6.如何保证参与对象的生命周期?
7.如何做到线程安全?在线程安全的前提下又如何保证不会出现竞态死锁?
。。。。。。
实现
针对上面提出的问题,一个一个来解决。
1.一个Observer可否观察多个Subject?
以后增加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.如何界定达到了最低耦合度?是否可以做到“无耦合”?
分析到目前为止,几乎就可以肯定,不使用多态的方式而改用范型来实现此模式,固然不存在抽象这个问题了。
由前面初步得出封装的模板类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类来完成这个事情,它的实现大致如下:
然后update接口将变成:
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自动转换函数这么裸露也就不安全了,返回数据之前也必须加锁:
如果Data类型是个大型集合或者是需要特定的访问方式,直接dt = data_;的方式显得受众过窄,也可以向重载update函数那样通过提供一个特定的访问functor,再把data_传给它,让它自己去根据需要进行访问就行了,如:
对象生命周期的问题。这里的参与者有两类,Subject与Observer。Observer调用Subject的任何函数只能通过boost::weak_ptr<Subject<T> >提升为boost::shared_ptr后调用,具体原因下次补上,还有test case和使用案例
修改完后,代码如下:
写不出介绍来,直接搜索,来自百度百科:观察者模式(有时又被称为发布-订阅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;
}
};