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.
评论