宋黎晓

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
    OECP社区用户积分与动态部分是使用基于观察者模式的思路实现的。观察者模式的介绍网上有好多,在这就只简单提一下,一带而过吧(如果以后有时间和必要再写一篇初级的让初学的朋友们看的)。这里主要介绍一下我们社区中利用这种模式的一个实现的思路,就作为观察者模式应用的一个实战范例吧.
    下面简单说下观察者模式是个什么。所谓“观察者”,就是观众,围观群众。但只有围观群众还构不成观察者模式,还需要有“被观察者”。观察者模式由“被观察者”和“观察者”共同构成。一个“被观察者”可能会有0到n个“观察者”。
    在我们社区中“被观察者”可以是“博客”、“用户”、“组件设计”、“讨论”、“留言”等等。“观察者”则是“积分”、“动态”、“邮件”、“站内信”、 “短信”等等。举个例子:当用户发表一篇博客,各个关注者将会被通知,积分系统得知后会给用户做加分,动态系统会增加动态信息,其他系统也会根据自己的需要做一些处理。
    其实观察者模式与java中的事件机制是非常相似的。当事件发生时,所有的监听器将会得到事件的信息,做自己分内的工作。所以“被观察者”我们可以认为是事件源EventSource,“观察者”就是监听器Listener。一个事件源可以有多个动作叫做Action,事件源发起动作称之为事件 Event。
    当事件源EventSource发生一个动作Action时,产生一个事件Event。所有的关注者Listener将的到这个Event,并根据Event中的信息,来完成自己要做的事情。这就是整个事件机制也就是观察者模式的大体过程。
    在实际的程序中,我们可能会有几个点需要解决一下。一个是,动作发生时,如何产生一个事件?另一个是,监听器如何关注事件并被通知到?对于第一个问题,我们只需要一个点火装置在动作发生时fire一下。解决第二个问题,我们需要一个可以容纳足够“观察者”的容器,就好像观看演出的剧场观众席。当事件发生时,依次通知这些“观察者”。
    那么整体的结构应该是这样的:

    通常情况下,监听机制有这些东西就可以建立了。具体的监听器,只要实现Listener接口,然后添加到事件源中就可以对事件源所发生的事件进行观察了。不过我觉得可能这样看起来不是很舒服,因为写程序时,我们应该尽可能让业务服务中少一些不相干的东西。比如上面EventSource里的 listeners和一些fireEvent这样的方法。所以改造一下成下面这种形式,可以让程序看起来干净一些。

    另外获得一个好处:经过这一步改造后,事件源与“事件类”、“监听器类”隔绝了,我们在系统中使用这种机制变得更加方便了。
    在实际的社区应用中事件机制就是基于这种关系组织建立起来的。为了更加便于开发和扩展,在应用时又做了一些扩充。
    由于社区中需要关注的事件并不是很多,一个事件管理器已经足够使用,我们可以采用使用同一套管理器管理多类事件并分发给相关监听者的方式。
    采用这种方式,首先要区分出事件的来源是什么,以便发生事件时通知相关的监听器。因此,增加了一个事件来源标识的枚举,为了区分事件来源,是博客、微博、讨论还是其他什么。
java 代码
  1. public enum EnumEventSourceSign {  
  2.     @EnumDescription("博客")  
  3.     blog,  
  4.     @EnumDescription("微博")  
  5.     micBlog,  
  6.     @EnumDescription("组件")  
  7.     bc,  
  8.     @EnumDescription("组件设计")  
  9.     bcWiki,  
  10.     @EnumDescription("组件讨论")  
  11.     bcTopic,  
  12.     @EnumDescription("其他")  
  13.     others,  
  14.     @EnumDescription("留言")  
  15.     leaveWord,  
  16.     @EnumDescription("活动")  
  17.     activity,  
  18.     @EnumDescription("项目管理")  
  19.     prj,  
  20.     @EnumDescription("资讯")  
  21.     news  
  22. }  
    监听器也可以使用统一接口:
java 代码
  1. public interface ActionListener {  
  2.   
  3.     public void onAction(ActionEvent event);  
  4.       
  5.     /** 
  6.      * 得到原标志 
  7.      * @author slx 
  8.      * @date 2010-7-9 上午10:04:35 
  9.      * @modifyNote 
  10.      * @return 
  11.      */  
  12.     EnumEventSourceSign getSourceSign();  
  13. }  
封装一个事件对象,把监听器处理事件时可能需要的内容放到里面
java 代码
  1. /** 
  2.  * 事件 
  3.  *  
  4.  * @author slx 
  5.  * @date 2010-7-7 下午05:27:37 
  6.  * @version 1.0 
  7.  */  
  8. public class ActionEvent<T> {  
  9.   
  10.     /** 当前用户 **/  
  11.     private User curUser;  
  12.   
  13.     /** 事件相关的对象 **/  
  14.     private T source;  
  15.   
  16.     private String action;  
  17.   
  18.     public ActionEvent(User curUser, T source, String action) {  
  19.         this.curUser = curUser;  
  20.         this.source = source;  
  21.         this.action = action;  
  22.     }  
  23.   
  24.     public User getCurUser() {  
  25.         return curUser;  
  26.     }  
  27.   
  28.     public T getSource() {  
  29.         return source;  
  30.     }  
  31.   
  32.     public String getAction() {  
  33.         return action;  
  34.     }  
  35.   
  36. }  
还需要一个事件管理器,来触发事件。
接口:
java 代码
  1. /** 
  2.  * 事件触发器接口 
  3.  * @author slx 
  4.  * @date 2010-7-7 下午05:31:59 
  5.  * @version 1.0 
  6.  */  
  7. public interface EventHandler {  
  8.    
  9.     /** 
  10.      * 发起事件 
  11.      * @author slx 
  12.      * @date 2010-7-7 下午05:31:59 
  13.      * @modifyNote 
  14.      * @param curUser 
  15.      *      User 当前用户 
  16.      * @param source 
  17.      *      Object 事件源 
  18.      * @param sourceSign 
  19.      *      EnumEventSourceSign 来源标志 
  20.      * @param action 
  21.      *      String 事件标示 
  22.      */  
  23.     public void fireEvent(User curUser ,Object source ,EnumEventSourceSign sourceSign, String action);  
  24.       
  25. }  
实现:
java 代码
  1. import java.util.ArrayList;  
  2. import java.util.LinkedHashMap;  
  3. import java.util.List;  
  4.   
  5. import com.posoft.user.eo.User;  
  6.   
  7. /** 
  8.  * 事件触发器 
  9.  * @author slx 
  10.  * @date 2010-7-7 下午04:50:31 
  11.  * @version 1.0 
  12.  */  
  13. public class EventHandlerImpl implements EventHandler {  
  14.       
  15.     private List<ActionListener> listeners;  
  16.       
  17.     private LinkedHashMap<EnumEventSourceSign, List<ActionListener>> m_listeners;  
  18.       
  19.     public void fireEvent(User curUser ,Object source ,EnumEventSourceSign sourceSign, String action){  
  20.         List<ActionListener> l = m_listeners.get(sourceSign);  
  21.         if(l!=null){  
  22.             ActionEvent event = new ActionEvent(curUser,source,action);  
  23.             for (ActionListener actionListener : l) {  
  24.                 actionListener.onAction(event);  
  25.             }  
  26.         }  
  27.     }  
  28.   
  29.     public void init(){  
  30.         m_listeners = new LinkedHashMap<EnumEventSourceSign, List<ActionListener>>();  
  31.         if(listeners!=null){  
  32.             for (ActionListener al : listeners) {  
  33.                 List<ActionListener> ls = m_listeners.get(al.getSourceSign());  
  34.                 if(ls == null){  
  35.                     ls = new ArrayList<ActionListener>();  
  36.                     m_listeners.put(al.getSourceSign(), ls);  
  37.                 }  
  38.                 ls.add(al);  
  39.             }  
  40.         }  
  41.     }  
  42.   
  43.     public void setListeners(List<ActionListener> listeners) {  
  44.         this.listeners = listeners;  
  45.     }  
  46.       
  47. }  
这里删除掉了添加监听器的add方法,因为使用了Spring的注入机制。所有监听器直接用Spring注入到了事件管理器中。
xhtml 代码
  1. <bean id="eventHandler" class="com.posoft.event.EventHandlerImpl"  
  2.         init-method="init">  
  3.         <property name="listeners">  
  4.             <!-- 请将需要的监听器添加到里面 监听器必须继承自 com.posoft.event.baselistener包中的类 -->  
  5.             <list>  
  6.                 <!-- 动态、通知监听开始 -->  
  7.                 <ref bean="BCWikiDynamicListener" />  
  8.                 <ref bean="BCTopicDynamicListener" />  
  9.                 .......  
  10.                 <!-- 动态、通知监听结束-->  
  11.                 <!-- 积分监听 开始 -->  
  12.                 <ref bean="bcPointsListener" />  
  13.                 <ref bean="bcTopicPointsListener" />  
  14.                 <ref bean="bcWikiPointsListener" />  
  15.                 .......  
  16.                 <!-- 积分监听 结束 -->  
  17.             </list>  
  18.         </property>  
  19.     </bean>  
这套事件机制基本的接口、类和配置有这些基本就可使用了。但是,为了代码写起来更简单更优雅,还可以做一些抽象和封装。
首先,目前的监听器现在只有一个onAction方法,显然不够用,虽然都是对一个事件源的监听,但是不同的动作,会做不同的处理,虽然也可以都用一个 onAction方法自行在内部区分动作来处理,但代码会很乱。因此,增加一个监听器的抽象类实现监听器接口,这个类负责区分动作,转向相应的方法(转向的规则是:转到与事件发生的动作名相同的监听器方法中)。实际的监听器,继承自这个类。
java 代码
  1. /** 
  2.  * 事件监听父类 
  3.  * @author slx 
  4.  * @date 2010-7-9 上午09:51:24 
  5.  * @version 1.0 
  6.  */  
  7. public abstract class BaseEventListener  implements ActionListener {  
  8.   
  9.     @Override  
  10.     public void onAction(ActionEvent event) {  
  11.         String action = event.getAction().toLowerCase();  
  12.         try {  
  13.             Class[] params = new Class[]{event.getClass()};  
  14.             Method md = this.getClass().getDeclaredMethod(action, params);  
  15.             md.invoke(thisnew Object[]{event});  
  16.         } catch (NoSuchMethodException e) {  
  17.             System.err.println("★★★★★★ 监听器未定义事件对应的处理方法: 监听器[" + this.getClass().getName()+ "] 监听动作 → " + action);  
  18.         } catch (Exception e) {  
  19.             throw new RuntimeException(e);  
  20.         }   
  21.     }  
  22. }  
使用这个类作为监听器的父类,需要遵守处理方法与需要监听的动作名称相同的规则。如果需要特别的规则,子类重写onAction方法即可。
这样子一共有了两个规则:
  1. 事件管理器通过枚举EnumEventSourceSign来区分事件的来源。监听器通过约定接口返回EnumEventSourceSign来标示自己身份。
  2. 监听器通过事件动作action的名称,来路由到相应的处理方法。
到了这一步,实现监听器的开发人员就可以根据约定来实现监听器了。不过这样还是不太舒服,我们根据不同的事件源定义一套监听器父类,定义好抽象的处理方法,让实际的实现人员集成过来填空岂不是更爽。例如这样:
java 代码
  1. /** 
  2.  * 博客相关事件监听 
  3.  * @author slx 
  4.  * @date 2010-7-8 上午10:39:51 
  5.  * @version 1.0 
  6.  */  
  7. public abstract class BlogBaseEventListener extends BaseEventListener {  
  8.       
  9.     @Override  
  10.     public final EnumEventSourceSign getSourceSign() {  
  11.         return EnumEventSourceSign.blog;  
  12.     }  
  13.   
  14.     /** 
  15.      * 发表博客 
  16.      * @author slx 
  17.      * @date 2010-7-9 上午09:45:00 
  18.      * @modifyNote 
  19.      * @param e 
  20.      */  
  21.     public abstract void addblog(ActionEvent<Blog> e);  
  22.       
  23.     /** 
  24.      * 删除博客 
  25.      * @author slx 
  26.      * @date 2010-7-9 上午09:45:25 
  27.      * @modifyNote 
  28.      * @param e 
  29.      */  
  30.     public abstract void delblog(ActionEvent<Blog> e);  
  31.       
  32.     /** 
  33.      * 从业务组件中删除博客 
  34.      * @author slx 
  35.      * @date 2010-7-8 下午04:49:05 
  36.      * @modifyNote 
  37.      * @param e 
  38.      *  ActionEvent<List> list(0)博客,list(1)业务组件 
  39.      */  
  40.     public abstract void delblogfrombc(ActionEvent<List> e);  
  41.       
  42.     /** 
  43.      * 评论博客 
  44.      * @author slx 
  45.      * @date 2010-7-8 下午04:49:05 
  46.      * @modifyNote 
  47.      * @param e 
  48.      *  ActionEvent<List> list(0)评论,list(1)博客 
  49.      */  
  50.     public abstract void comment(ActionEvent<List> e);  
  51.       
  52.     /** 
  53.      * 删除博客评论 
  54.      * @author slx 
  55.      * @date 2010-7-8 下午04:49:05 
  56.      * @modifyNote 
  57.      * @param e 
  58.      *  ActionEvent<List> list(0)评论,list(1)博客 
  59.      */  
  60.     public abstract void delcomment(ActionEvent<List> e);  
  61.       
  62.     /** 
  63.      * 博客投票 
  64.      * @author slx 
  65.      * @date 2010-10-27 下午03:01:13 
  66.      * @modifyNote 
  67.      * @param e 
  68.      */  
  69.     public abstract void vote(ActionEvent<BlogVote> e);  
  70. }  
如此一来,开发监听器的只需要集成相应的监听器父类,然后将自己的实现配置到Spring的配置文件中让它被注入到事件管理器中就可以了。
总体结构如下图:

最后总结一下工作步骤:
  1. 服务类使用事件管理器EventHandler的fire方法发起事件,发起事件需要传入几个参数:当前用户、事件源、来源标识、动作名称。
  2. 有了上面传入的参数,事件管理器就可以根据这些参数创建事件对象ActionEvent了,然后根据来源标识,调用注册到管理器中的相关Listener的onAction方法。
  3. Listener的onAction方法中,从Event中取得事件动作名,调用自身与动作名同名的方法完成自己分内的业务。

在这套机制中,事件管理器,使用同一个,所有事件源的监听都注册到这个管理器中。这种模式适合事件比较少的系统中,如果事件比较多,还是建议拆分成多套类似的机制来处理,以免服务器内存吃不消。

本文是由作者本人发到博客园,原文:http://www.oecp.cn/hi/slx/blog/2206

这里人气旺,特来收集大家的意见.欢迎讨论.

posted on 2011-03-30 10:07  宋黎晓  阅读(1390)  评论(0编辑  收藏  举报