观察者模式和Tomcat, Guava的EventBus中的简单实现

1. 简介

    观察者模式,也叫发布-订阅模式, 定义了一对多的依赖关系. 多个观察者监听一个主题.

    主题发生变化时, 观察者得到通知, 然后选择后续的动作. 

    这里的得到通知, 根据是主题主动通知还是观察者主动请求分为推模型和拉模型

    类图如下:

    
                

    上图是观察者模式的最简单实现,类似于OS中的最小系统. 和一般面向对象语言的设计模式一样, 都会使用到多态, 利于程序的可扩展性.类图中分为4个角色:

    抽象主题: Subject 

    具体主题: ConcreteSubject

    抽象观察者: Observer

    具体观察者: ConcreteObserver

    

    观察者只有一个方法update(). 主题Subject中, 有3个方法 add() 或 register(), remove(), notify().

    最主要的是notify(), 一般是主题遍历自己维护的观察者队列中的所有观察者, 一次调用观察者的update(). 

    这里有一个思维的误区, 观察者收到通知,然后自己选择是否进行操作. 实际上是一个被动的过程. 

    首先观察者将自己注册到主题中, 主题维护一个队列,用于持有该观察者的引用. 之后由主题调用观察者的方法进行更新.

2. tomcat中的应用

    这里借用网络上的一个例子. 

    tomcat中,定义了一个LifeCycleSupport类, 协助管理观察者, 其实也是对观察者模式的一个延伸. 

    fireLifeCycleEvent里面有一个fireLifeCycleEvent()方法,也就是类图中的notify().

   

public void fireLifeCycleEvent (String type, Object data) {
     LifeCycleEvent event = new LifeCycleEvent(lifecycle, type, data);
     LifeCycleListener linterested = listeners;
     for (int i = 0; i < interested.length; i++) {
           interested[i].llfecycleEvent(event);
     }

     

     参数中的LifecycleEvent 是tomcat定义的事件, 如下:

   

public LifecycleEvent (Lifecycle lifecycle, String type, Object data) {
    super(lifecycle);
    this.type = type;
    this.data = data;
}

   

    从上可知,tomcat中观察者的流程如下:

    1. 将观察者listener加到LifeCycleSupport的listener数组中

    2. 主题做某些动作时, 生成一个LifeCycleEvent对象, 标明这是一个什么样的事件. 之后由LifeCycleSupport通知listener中的每一个观察者,

    3. listener会根据事件的类型, 做出对应的动作
 

3. 观察者应用的场景

    观察者适用于几个场景

    ①一个对象的改变需要通知到多个对象. 但是又不清楚具体有多少个对象需要通知,. 

       观察者使用了集合 + 接口实线了解耦, 使Subject和Observer都只知道对方是一个接口, 不用关心具体的子类

    ②多级联动关系, 比如GUI编程中. panet A 中有多个panet B , 当panet A 需要改变时, panet B 大小也需要随着改变, 整体全部变化

    ③ 消息分发场景, 比如QQ广播, 新闻推送. 当然这些功能并非一定依靠观察者才能实现

4. Guava中的实现

    guava中的观察者非常简单. 只需要在指定的方法加上注解 @Subscribe即可.

    

public class Event {
    @Subscribe
    public void sub(String msg) {
       //some statement
       System.out.println("sub method is called."  + msg);
    }
}


public class Client {
public static void main(String[] args) {
        EventBus eventbus = new EventBus();
        eventbus.register(new Event());
        eventbus.post("Guava EventBus is post"); //触发事件
    }
}

   EventBus的大体实现逻辑

   调用eventbus.register()时, eventbus实例会将事件对象放到SetMultimap<Class<?>, EventSubscriber> 中, 这是一个线程安全的容器, unregister()方法也在其中

    再说eventbus.post()方法. eventbus实例将参数匹配的对象放到事件队列 ThreadLocal<Queue<EventWithHandler>> eventsToDispatch中, 等待事件分发完成. 再做统一的事件消费.

这里所有的容器都是线程安全的, 本地线程, 同步, 同时使用ReentranceLock.

    另外EventBus提供了一个异步容器 AsyncEventBus, 示例代码如下:

    

public void asyncEventBus() {
    AsyncEventBus eventbus = new AsyncEventBus(Executor.newFixedThreadPool());
    eventbus.register(new Event());
    eventbus.post("async event bus called");
}

   

    AsyncEventBus实线逻辑中, 事件注册, 移除, 和同步的EventBus是相同的逻辑, 不同的是事件的分发和消费.它的事件消费,不再使用ThreadLocal, 而是换成了ConcurrentLinkedQueue<EventWithHandler> eventsToDispatch. 消费事件任务是一个线程池, 一个Executor的实现, 这个实现需要开发自己定义.

   最后是deadEvent, 使用场景如下:

   

public static void main(String[] args) {
        EventBus eventbus = new EventBus();
        eventbus.register(new Event());
        eventbus.post(123); //这里应该是使用String对象作为参数,但这里参数类型是int
    }

    这里@Subscribe方法的参数类型是String, 但是@post中的参数类型是int, 导致@Subscribe方法无法被消费, EventBus会将此事件当做一个deadEvent. 

猜你喜欢

转载自linbeisaii.iteye.com/blog/2244583
今日推荐