社区性质网站事件驱动机制-观察者模式应用
OECP社区用户积分与动态部分是使用基于观察者模式的思路实现的。观察者模式的介绍网上有好多,在这就只简单提一下,一带而过吧(如果以后有时间和必要再写一篇初级的让初学的朋友们看的)。这里主要介绍一下我们社区中利用这种模式的一个实现的思路,就作为观察者模式应用的一个实战范例吧.
下面简单说下观察者模式是个什么。所谓“观察者”,就是观众,围观群众。但只有围观群众还构不成观察者模式,还需要有“被观察者”。观察者模式由“被观察者”和“观察者”共同构成。一个“被观察者”可能会有0到n个“观察者”。
在我们社区中“被观察者”可以是“博客”、“用户”、“组件设计”、“讨论”、“留言”等等。“观察者”则是“积分”、“动态”、“邮件”、“站内信”、 “短信”等等。举个例子:当用户发表一篇博客,各个关注者将会被通知,积分系统得知后会给用户做加分,动态系统会增加动态信息,其他系统也会根据自己的需要做一些处理。
其实观察者模式与java中的事件机制是非常相似的。当事件发生时,所有的监听器将会得到事件的信息,做自己分内的工作。所以“被观察者”我们可以认为是事件源EventSource,“观察者”就是监听器Listener。一个事件源可以有多个动作叫做Action,事件源发起动作称之为事件 Event。
当事件源EventSource发生一个动作Action时,产生一个事件Event。所有的关注者Listener将的到这个Event,并根据Event中的信息,来完成自己要做的事情。这就是整个事件机制也就是观察者模式的大体过程。
在实际的程序中,我们可能会有几个点需要解决一下。一个是,动作发生时,如何产生一个事件?另一个是,监听器如何关注事件并被通知到?对于第一个问题,我们只需要一个点火装置在动作发生时fire一下。解决第二个问题,我们需要一个可以容纳足够“观察者”的容器,就好像观看演出的剧场观众席。当事件发生时,依次通知这些“观察者”。
那么整体的结构应该是这样的:
通常情况下,监听机制有这些东西就可以建立了。具体的监听器,只要实现Listener接口,然后添加到事件源中就可以对事件源所发生的事件进行观察了。不过我觉得可能这样看起来不是很舒服,因为写程序时,我们应该尽可能让业务服务中少一些不相干的东西。比如上面EventSource里的 listeners和一些fireEvent这样的方法。所以改造一下成下面这种形式,可以让程序看起来干净一些。
另外获得一个好处:经过这一步改造后,事件源与“事件类”、“监听器类”隔绝了,我们在系统中使用这种机制变得更加方便了。
在实际的社区应用中事件机制就是基于这种关系组织建立起来的。为了更加便于开发和扩展,在应用时又做了一些扩充。
由于社区中需要关注的事件并不是很多,一个事件管理器已经足够使用,我们可以采用使用同一套管理器管理多类事件并分发给相关监听者的方式。
采用这种方式,首先要区分出事件的来源是什么,以便发生事件时通知相关的监听器。因此,增加了一个事件来源标识的枚举,为了区分事件来源,是博客、微博、讨论还是其他什么。
- public enum EnumEventSourceSign {
- @EnumDescription("博客")
- blog,
- @EnumDescription("微博")
- micBlog,
- @EnumDescription("组件")
- bc,
- @EnumDescription("组件设计")
- bcWiki,
- @EnumDescription("组件讨论")
- bcTopic,
- @EnumDescription("其他")
- others,
- @EnumDescription("留言")
- leaveWord,
- @EnumDescription("活动")
- activity,
- @EnumDescription("项目管理")
- prj,
- @EnumDescription("资讯")
- news
- }
- public interface ActionListener {
- public void onAction(ActionEvent event);
- /**
- * 得到原标志
- * @author slx
- * @date 2010-7-9 上午10:04:35
- * @modifyNote
- * @return
- */
- EnumEventSourceSign getSourceSign();
- }
- /**
- * 事件
- *
- * @author slx
- * @date 2010-7-7 下午05:27:37
- * @version 1.0
- */
- public class ActionEvent<T> {
- /** 当前用户 **/
- private User curUser;
- /** 事件相关的对象 **/
- private T source;
- private String action;
- public ActionEvent(User curUser, T source, String action) {
- this.curUser = curUser;
- this.source = source;
- this.action = action;
- }
- public User getCurUser() {
- return curUser;
- }
- public T getSource() {
- return source;
- }
- public String getAction() {
- return action;
- }
- }
接口:
- /**
- * 事件触发器接口
- * @author slx
- * @date 2010-7-7 下午05:31:59
- * @version 1.0
- */
- public interface EventHandler {
- /**
- * 发起事件
- * @author slx
- * @date 2010-7-7 下午05:31:59
- * @modifyNote
- * @param curUser
- * User 当前用户
- * @param source
- * Object 事件源
- * @param sourceSign
- * EnumEventSourceSign 来源标志
- * @param action
- * String 事件标示
- */
- public void fireEvent(User curUser ,Object source ,EnumEventSourceSign sourceSign, String action);
- }
- import java.util.ArrayList;
- import java.util.LinkedHashMap;
- import java.util.List;
- import com.posoft.user.eo.User;
- /**
- * 事件触发器
- * @author slx
- * @date 2010-7-7 下午04:50:31
- * @version 1.0
- */
- public class EventHandlerImpl implements EventHandler {
- private List<ActionListener> listeners;
- private LinkedHashMap<EnumEventSourceSign, List<ActionListener>> m_listeners;
- public void fireEvent(User curUser ,Object source ,EnumEventSourceSign sourceSign, String action){
- List<ActionListener> l = m_listeners.get(sourceSign);
- if(l!=null){
- ActionEvent event = new ActionEvent(curUser,source,action);
- for (ActionListener actionListener : l) {
- actionListener.onAction(event);
- }
- }
- }
- public void init(){
- m_listeners = new LinkedHashMap<EnumEventSourceSign, List<ActionListener>>();
- if(listeners!=null){
- for (ActionListener al : listeners) {
- List<ActionListener> ls = m_listeners.get(al.getSourceSign());
- if(ls == null){
- ls = new ArrayList<ActionListener>();
- m_listeners.put(al.getSourceSign(), ls);
- }
- ls.add(al);
- }
- }
- }
- public void setListeners(List<ActionListener> listeners) {
- this.listeners = listeners;
- }
- }
- <bean id="eventHandler" class="com.posoft.event.EventHandlerImpl"
- init-method="init">
- <property name="listeners">
- <!-- 请将需要的监听器添加到里面 监听器必须继承自 com.posoft.event.baselistener包中的类 -->
- <list>
- <!-- 动态、通知监听开始 -->
- <ref bean="BCWikiDynamicListener" />
- <ref bean="BCTopicDynamicListener" />
- .......
- <!-- 动态、通知监听结束-->
- <!-- 积分监听 开始 -->
- <ref bean="bcPointsListener" />
- <ref bean="bcTopicPointsListener" />
- <ref bean="bcWikiPointsListener" />
- .......
- <!-- 积分监听 结束 -->
- </list>
- </property>
- </bean>
首先,目前的监听器现在只有一个onAction方法,显然不够用,虽然都是对一个事件源的监听,但是不同的动作,会做不同的处理,虽然也可以都用一个 onAction方法自行在内部区分动作来处理,但代码会很乱。因此,增加一个监听器的抽象类实现监听器接口,这个类负责区分动作,转向相应的方法(转向的规则是:转到与事件发生的动作名相同的监听器方法中)。实际的监听器,继承自这个类。
- /**
- * 事件监听父类
- * @author slx
- * @date 2010-7-9 上午09:51:24
- * @version 1.0
- */
- public abstract class BaseEventListener implements ActionListener {
- @Override
- public void onAction(ActionEvent event) {
- String action = event.getAction().toLowerCase();
- try {
- Class[] params = new Class[]{event.getClass()};
- Method md = this.getClass().getDeclaredMethod(action, params);
- md.invoke(this, new Object[]{event});
- } catch (NoSuchMethodException e) {
- System.err.println("★★★★★★ 监听器未定义事件对应的处理方法: 监听器[" + this.getClass().getName()+ "] 监听动作 → " + action);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
- }
这样子一共有了两个规则:
- 事件管理器通过枚举EnumEventSourceSign来区分事件的来源。监听器通过约定接口返回EnumEventSourceSign来标示自己身份。
- 监听器通过事件动作action的名称,来路由到相应的处理方法。
- /**
- * 博客相关事件监听
- * @author slx
- * @date 2010-7-8 上午10:39:51
- * @version 1.0
- */
- public abstract class BlogBaseEventListener extends BaseEventListener {
- @Override
- public final EnumEventSourceSign getSourceSign() {
- return EnumEventSourceSign.blog;
- }
- /**
- * 发表博客
- * @author slx
- * @date 2010-7-9 上午09:45:00
- * @modifyNote
- * @param e
- */
- public abstract void addblog(ActionEvent<Blog> e);
- /**
- * 删除博客
- * @author slx
- * @date 2010-7-9 上午09:45:25
- * @modifyNote
- * @param e
- */
- public abstract void delblog(ActionEvent<Blog> e);
- /**
- * 从业务组件中删除博客
- * @author slx
- * @date 2010-7-8 下午04:49:05
- * @modifyNote
- * @param e
- * ActionEvent<List> list(0)博客,list(1)业务组件
- */
- public abstract void delblogfrombc(ActionEvent<List> e);
- /**
- * 评论博客
- * @author slx
- * @date 2010-7-8 下午04:49:05
- * @modifyNote
- * @param e
- * ActionEvent<List> list(0)评论,list(1)博客
- */
- public abstract void comment(ActionEvent<List> e);
- /**
- * 删除博客评论
- * @author slx
- * @date 2010-7-8 下午04:49:05
- * @modifyNote
- * @param e
- * ActionEvent<List> list(0)评论,list(1)博客
- */
- public abstract void delcomment(ActionEvent<List> e);
- /**
- * 博客投票
- * @author slx
- * @date 2010-10-27 下午03:01:13
- * @modifyNote
- * @param e
- */
- public abstract void vote(ActionEvent<BlogVote> e);
- }
总体结构如下图:

最后总结一下工作步骤:
- 服务类使用事件管理器EventHandler的fire方法发起事件,发起事件需要传入几个参数:当前用户、事件源、来源标识、动作名称。
- 有了上面传入的参数,事件管理器就可以根据这些参数创建事件对象ActionEvent了,然后根据来源标识,调用注册到管理器中的相关Listener的onAction方法。
- Listener的onAction方法中,从Event中取得事件动作名,调用自身与动作名同名的方法完成自己分内的业务。
在这套机制中,事件管理器,使用同一个,所有事件源的监听都注册到这个管理器中。这种模式适合事件比较少的系统中,如果事件比较多,还是建议拆分成多套类似的机制来处理,以免服务器内存吃不消。
本文是由作者本人发到博客园,原文:http://www.oecp.cn/hi/slx/blog/2206
这里人气旺,特来收集大家的意见.欢迎讨论.