模板应用--观察者模式

来源:互联网 发布:php fpm 执行脚本原理 编辑:程序博客网 时间:2024/05/21 09:09
介绍
    写不出介绍来,直接搜索,来自百度百科:观察者模式(有时又被称为发布-订阅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 changedbreak;    case type_b:// subject b changedbreak;    default:// othersbreak;}    }

以后增加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 {updatersender_;Dataold_data_;Datanew_data_;};template <class Data, class Event>class subject : public boost::noncopyable{public:typedef boost::function<void (Event evt)>observer_obj;typedef Datadata_type;typedef Eventevt_type;typedefstd::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_typedata_;/* ** hold all of the observers that interest in data*/observer_objsobservers_;//!boolchanging_;/* ** notify all observers that the data has changed*/voidnotify(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*/boolregister_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.*/boolunregister_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*/voidupdate(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*/voidupdate(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接口将变成:

boolupdate(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自动转换函数这么裸露也就不安全了,返回数据之前也必须加锁:

boolget_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>voidaccessed_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 {updatersender_;Dataold_data_;Datanew_data_;};template <class Data, class Event>class subject : public boost::noncopyable{public:typedef boost::function<void (Event evt)>observer_obj;typedef Datadata_type;typedef Eventevt_type;typedefstd::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_mutexmutex;protected:/* ** the original data that need to be monitored*/data_typedata_;/* ** hold all of the observers that interest in data*/observer_objsobservers_;//! mutexmtx_;//!boolchanging_;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*/voidnotify(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*/boolget_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>voidaccessed_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*/boolregister_obs(void* ptr, const typename this_type::observer_obj& obs){// maybe block hereboost::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.*/boolunregister_obs(int key){// maybe block hereboost::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*/boolupdate(const Data& data, Event evt){// maybe block hereboost::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*/boolupdate(special_updater op){// maybe block hereboost::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;}};



原创粉丝点击