本文分为三个部分:
 
  • Observer(观察者)
  • Guava EventBus详解
  • Guava EventBus使用示例
 
1. Observer(观察者)
 
1.1 背景
 
我们设计系统时,常常会将系统分割为一系列相互协作的类,使得这些类之间可以各自独立地复用,系统整体结构也会比较清晰。这是一种最基本的面向对象的设计方式,大多数情况下也非常有效。但是,如果这些相互协作的类之间的“协作”关系比较复杂,那么就会有副作用:需要维护这些类对象间的状态一致性。
 
我们以一个数据可视化系统为例来说明:
 
(1)数据可视化系统可以分割为两个主要的类:定义应用数据的类和负责界面表示的类;换言之,数据可视化系统中存在两类对象:应用数据对象(用于封装底层数据源中的数据)和界面对象(如:表格对象、柱形图对象、饼图对象等等);
 
(2)数据可视化系统中类(对象)之前的协作关系:如果应用数据对象发生变化(表示底层数据源中的数据发生变化),则表格对象、柱形图对象、饼图对象需要被立即更新;反过来也是如此,如果表格对象发生变化(假设表格对象的变化会导致底层数据源中的数据发生变化),则数据对象需要被立即更新,从而导致柱形图对象、饼图对象也需要被更新;
 
数据对象、表格对象、柱形图对象、饼图对象之间的协作关系如下:
 
 
表格对象、柱形图对象、饼图对象分别使用不同的表示形式描述同一个数据对象的信息,它们之间相互并不知道对方的存在,这样我们可以根据需要单独复用这些对象(类)。但在我们数据可视化系统的场景中,它们表现的“互相知道”(参见上述(2))。
 
这种“互相知道”的行为意味着表格对象、柱形图对象、饼图对象都需要依赖于数据对象,数据对象的任何状态变化都应立即通过它们,某一界面对象的变化实际也是通过数据对象反映给其它界面对象的。同时也没有理由将依赖于数据对象的界面对象数目限定为三个,对相同的数据可以有任意数目的不同用户界面。
 
综上所述,数据、表格、柱形图、饼图这几个互相协作的类的副作用表现为:需要维护数据对象、表格对象、柱形图对象(可能还有其它界面对象)之间状态的一致性,某一对象的状态发生变化,需要立即更新其它对象的状态。
 
1.2 定义
 
观察者模式用于定义对象间的一种一对多的依赖关系,当一个对象的状态发生变化时,所有依赖于它的对象都将得到通知并被“自动”更新。它有两个关键对象:目标(Subject)和观察者(Observer)。一个目标可以有任意数量的依赖它的观察者。一旦目标的状态发生改变,所有的观察者都得到通知。作为对这个通知的响应,每个观察者都将查询目标以使其状态与目标状态同步。
 
这种交互也称为“发布—订阅”(publish-subscribe)。目标是通知的发布者。它发出通知时并不需要知道谁是它的观察者。可以有任意数目的观察者订阅并接收通知。
 
注:参考1.1中的数据可视化系统示例,“目标”为数据对象,“观察者”为表格对象、柱形图对象、饼图对象。
 
1.3 结构与协作
 
 
Subject(目标)
 
—目标知道它的观察者,可以有任意多个观察者观察同一个目标。
—提供注册和删除观察者对象的接口(attach、detach)。
 
Observer(观察者)
 
—为那些在目标发生改变时需要获得通知的对象定义一个更新接口(update)。
 
ConcreteSubject(具体目标)
 
—将有关状态存入各个ConcreteSubject对象。
—当它的状态发生变化时,向它的各个观察者发出通知(notify)。
 
ConcreteObserver(具体观察者)
 
—维护一个指向ConcreteSubject对象的引用。
—存储有关状态,这些状态应与目标的状态保持一致。
—实现Observer的更新接口以使自身状态与目标的状态保持一致。
 
下面的交互图说明了一个目标和两个观察者之间的协作:
 
 
(1)当ConcreteSubject发生任何可能导致其观察者与其本身状态不一致的改变时(aConcreteSubject的改变请求由aConcreteObserver通过setState()发出),它将通知它的各个观察者(notify());
(2)ConcreteObserver对象在得到一个具体的改变通知后,可向目标对象查询信息(getState()),并使用这些信息使它的状态与目标对象的状态一致。
 
注意:
 
(1)发出改变请求的Observer对象并不立即更新,而是将其推迟到它从目标得到一个通知之后;
(2)notify()不总是由目标对象调用,它也可被一个观察者或其它对象调用;
 
1.4 更改管理器(ChangeManager)
 
当目标和观察者间的依赖关系特别复杂时,就需要一个维护这些关系的对象,我们称之为更改管理器(ChangeManager)。
 
 
ChangeManager有三个责任:
 
(1)它将一个目标映射到它的观察者并提供相应的接口(register、unregister)来维护这个映射,这就不需要由目标来维护对其观察者的引用,反之亦然;
(2)它定义一个特定的更新策略(这里的更新策略是更新所有依赖于这个目录的观察者);
(3)根据一个目标的请求(notify),它更新所有依赖于这个目标的观察者;
 
2.  Guava EventBus详解
 
通常情况下,一个系统(这里特指进程)内部各个组件之间需要完成事件分发或消息通信,往往需要这些组件之间显式地相互引用。如果这些组件数目较多,且相互引用关系复杂就会出现副作用:需要维护这些相互引用的组件之间的状态一致性。
 
观察者模式(Observer)用于解决上述问题,EventBus就是该模式的一个实现,它是Google Guava提供的一个组件。
 
EventBus使用“发布—订阅”的通信模式,使得系统(进程)内各个组件之间无需相互显式引用即可完成事件分发或消息通信,如下图所求:
 
 
它的设计结构非常符合观察者模式,
 
目标:事件(Event);
观察者:事件监听器(EventListener)(EventHandler是EventListener的封闭);
 
每一次事件的发生或变化,EventBus负责将其派发(post)至相应的事件监听器,同一事件的事件监听器可以有多个。
 
2.1 EventBus register
 
2.1.1 作用
 
维护事件与事件监听器之间的对应关系,如果某一事件发生,可以从对应关系中查找出应该将该事件派发至哪些事件监听器。
 
2.1.2 事件(Event)与事件监听器(EventListener)
 
 
EventBus并不强制要求事件(Event)与事件监听器(EventListener)必须继承特定的类或实现特定的接口,普通的Java类即可。这是因为事件(Event)就是一个对象,它保存着特定时间点的特定状态,而事件监控器(EventListener)实质就是一个方法(Method),即发生特定事件就执行该方法,所以理论上这两者可以是任意的普通类。那么EventBus使用什么策略从一个普通的Java类中识别出事件(Event)与事件监听器(EventListener),从而维护它们之间的对应关系?既然是一个普通的Java类,那么策略应该是多种多样的,EventBus为此设计了一个策略接口:HandlerFindingStrategy,如下图所示:
 
这里首先说明一下EventHandler(事件处理器)。
 
如上所述,事件监控器(EventListener)实质就是一个方法(Method),而方法的调用需要目标对象target、目标方法method的共同参与,EventHandler对这两者信息进行了封装,后续讨论皆以事件处理器(EventHandler)表示事件监听器(EventListener)。
 
HandlerFindingStrategy仅仅有一个方法:Multimap<Class<?>, EventHandler> findAllHandlers(Object source),它代表着策略的抽象过程:从传入的类实例对象source中寻找出所有的事件(Event)与事件处理器(EventHandler)的对应关系。注意,该方法的返回值为Multimap,这是一种特殊的Map,一个键可以对应着多个值,它表示一个事件可以有多个事件处理器与之对应;其中,键为事件对象类实例,值为事件处理器实例。
 
目前,EventBus仅仅提供一种HandlerFindingStrategy的实现:AnnotatedHandlerFinder,它是一种基于注解(Annotation)的实现,
 
 
以类实例对象listener为例说明一下工作过程:
 
(1)获取实例对象listener的类实例clazz;
(2)获取类实例clazz的所有方法,并依次迭代处理,假设其中的一个方法为method:
               a. 如果method标记有注解“Subscribe”,且method只有一个参数,则表示method可以作为事件监听器,继续处理;否则继续处理下一个method;
               b. method的这个参数类型即为事件类型eventType;
               c. 通过makeHandler()将实例对象listener、方法method封装为handler(事件处理器);
               d. 维护eventType、handler之间的对应关系,将其保存至methodsInListener;
(3)获取类实例clazz的父类实例,将其保存至clazz,如果clazz不为null,则继续(2);否则结束;
 
makeHandler()工作过程实际就是构建EventHandler对象,如下所示:
 
 
如果方法method标记有注解AllowConcurrentEvents,则表示该方法可以被事件处理器在多线程环境下线程安全的访问,直接使用EventHandler封装即可;如果方法method没有标记有注解AllowConcurrentEvents,则表示该方法无法被事件处理器在多线程环境下线程安全的访问,需要使用SynchronizedEventHandler封装。SynchronizedEventHandler继承自EventHandler,仅有一处不同:
 
 
即使用关键字synchronized修饰方法handleEvent,使其可以在多线程环境下被安全地访问。
 
有几点需要注意:
 
(1)事件与事件处理器之间的对应关系是依靠事件类型(eventType)连接起来的,而事件类型(eventType)就是事件监听器方法的参数类型;
(2)类实例对象listener的任何一个方法,只要它含有注解Subscribe且只有一个参数,就可以作为事件监听器或事件处理器;
(3)类实例对象的所有父类都会参与上述工作过程;
 
2.1.3 register
 
 
handlersByType:SetMultimap实例,用于维护EventBus内部所有的事件与事件处理器的对应关系(SetMultimap、Multimap的使用方法可以参考Google Guava的相关文档);
 
finder:AnnotatedHandlerFinder实例;
 
object:类实例对象,用于从中寻找出事件与事件处理器的对应关系;handlersByType某一事件对应的事件处理器可能来自于不同的类实例对象object;
 
2.2 EventBus unregister
 
EventBus unregister就是从handlersByType中移除类实例对象object中包含的所有事件与事件处理器的对应关系,工作过程比较简单,不再赘述。
 
 
2.3 EventBus post
 
EventBus post大致可以分为以下三个过程:
 
 
2.3.1 flattenHierarchy
 
EventBus post event时,event整个继承关系树中所有类和接口对应的事件处理器都会参考到事件派发的过程中来,flattenHierarchy就是用于获取event整个继承关系树中所有类和接口的类实例的,每一个类实例(Class)表示一个事件类型:
 
 
因为这个继承关系树在系统(进程)的运行过程中不会发生变化(不考虑热加载的情况),这里使用了缓存技术,用于缓存某个对象的继承关系树,使用Google Guava LoadingCache构建,我们不对此详细展开讨论,仅仅阐述缓存没有命中时的处理情况:
 
 
可以看出,整个继承关系树中的类和接口都被获取。
 
2.3.2 enqueueEvent
 
依次为每个事件类型对应的事件处理器派发事件,此时事件处理器并没有被实际执行,而是以EventWithHandler对象的形式被存入一个队列。
 
 
getHandlersForEventType:用于获取事件类型对应的所有事件处理器;
 
enqueueEvent:用于将事件(Event)和事件处理器(EventHandler)封装为EventWithHandler放入队列;
 
EventWithHandler如下:
 
 
enqueueEvent如下:
 
 
eventsToDispatch是一个ThreadLocal<ConcurrentLinkedQueue<EventWithHandler>>变量,也就是每一个post线程内部都有一个队列,用于存放EventWithHandler对象。
 
疑问:是否需要使用ConcurrentLinkedQueue?
 
2.3.3 dispatchQueuedEvents
 
dispatchQueuedEvents的过程其实就是执行队列中的事件处理器,过程如下:
 
 
 
可以看出,队列中的事件处理器是依次被执行的。
 
疑问:是否需要使用isDispatching?
 
EventBus是一种同步实现(即事件处理器是被依次触发的),另外有一种异步实现AsyncEventBus,核心原理相同,有兴趣的读者可自行研究。
 
3. Guava EventBus使用示例
 
 
输出结果:
 
EventHandler handle Event
EventHandler2 handle Event2
 
 
 
 
 
 
 
 
 
 
 

设计模式:Observer(观察者)—— Guava EventBus的更多相关文章

  1. Java设计模式——Observer(观察者)模式

    在多个对象之间建立一对多的关系,以便当一个对象状态改变的时候.其它全部依赖于这个对象的对象都能得到通知,并被自己主动更新. 适用情况: 当一个抽象模型有两个方面,当中一个方面依赖于还有一方面. 将这二 ...

  2. 设计模式:观察者(Observer)模式

    设计模式:观察者(Observer)模式 一.前言   观察者模式其实最好的名称应该是“发布订阅”模式,和我们现在大数据之中的发布订阅方式比较类似,但是也有区别的地方,在上一个设计模式,我们学习的是仲 ...

  3. iOS设计模式(01):观察者

    iOS设计模式(01):观察者 iOS-Observer-Pattern 什么是观察者模式 什么是观察者模式?你曾经订阅过报纸吗?在订阅报纸的时候,你不用去任何地方,只需要将你的个人地址信息以及订阅信 ...

  4. Guava - EventBus(事件总线)

    Guava在guava-libraries中为我们提供了事件总线EventBus库,它是事件发布订阅模式的实现,让我们能在领域驱动设计(DDD)中以事件的弱引用本质对我们的模块和领域边界很好的解耦设计 ...

  5. Guava 12:Guava EventBus源码剖析

    一.架构速读 传统上,Java的进程内事件分发都是通过发布者和订阅者之间的显式注册实现的.设计EventBus就是为了取代这种显示注册方式,使组件间有了更好的解耦.EventBus不是通用型的发布-订 ...

  6. [Guava] EventBus

    1.  发布-订阅模式 发布-订阅模式(publish-subscribe)是一种编程范式,发布方不发布消息给特定的接收方,而是由订阅方选择性接收.这使得发布方和订阅方相对独立,减少了耦合性. 在发布 ...

  7. java: 观察者模式:Observable被观察者,Observer观察者

    java: 观察者模式:Observable被观察者,Observer观察者 以房子价格为例,卖房者为被观察者: import java.util.Observable; //被观察者子类化 publ ...

  8. Java 设计模式 – Observer 观察者模式

    目录 [隐藏] 1 代码 1.1 观察者接口: 1.2 被观察者: 1.3 观众类 : 1.4 电影类: 1.5 效果如下: 代码 说明都在注释: 观察者接口: package ObserverMod ...

  9. (java)从零开始之--观察者设计模式Observer

    观察者设计模式:时当一个对象发生指定的动作时,要通过另外的对象做出相应的处理. 步骤: 1. A对象发生指定的动作是,要通知B,C,D...对象做出相应的处理,这时候应该把B,C,D...对象针对A对 ...

随机推荐

  1. mysql子查询优化

    ,,,) ) LIMIT 第一种方式in where:2000ms SELECT COUNT(*) AS tp_count FROM xxx_b2c_orders o ,,,) and from xx ...

  2. 【BZOJ 1084】[SCOI2005]最大子矩阵

    Description 这里有一个n*m的矩阵,请你选出其中k个子矩阵,使得这个k个子矩阵分值之和最大.注意:选出的k个子矩阵不能相互重叠. Input 第一行为n,m,k(1≤n≤100,1≤m≤2 ...

  3. 利用JQuery的$.ajax()可以很方便的调用asp.net的后台方法

    利用JQuery的$.ajax()可以很方便的调用asp.net的后台方法. 先来个简单的实例热热身吧. 1.无参数的方法调用 asp.net code: view plaincopy to clip ...

  4. Web Api 控制器

    Web Api 控制器 文档目录 本节内容: 简介 AbpApiController 基类 本地化 其它 过滤 审计日志 授权 防伪造过滤 工作单元 结果包装和异常处理 结果缓存 验证 模块绑定器 简 ...

  5. python学习笔记9(对文件的操作)

    一.文件对象 我理解的文件对象就是一个接口,通过这个接口对文件进行相关操作. 二.相关函数 [1].内建函数:open() 提供了初始化输入/输出(I/O)操作的通用接口,成功打开一个文件后会返回一个 ...

  6. Executing a script from Nagios event handler fails to run

    I have Nagios running on a webserver. For this one Nagios service check in particular, if it fails, ...

  7. SOCI、LiteSQL、POCO数据库访问类库对比

    最近在做视频的开发,其中视频的设备接入管理服务器.流媒体管理服务器.中心服务器都涉及到了数据库的操作,同时需要兼容大多数版本的数据库,包括mysql.sqlite.oracle.公司原来使用的是ado ...

  8. linux ps查看进程命令

    linux ps查看进程命令ps命令作用:将某个时间点的程序运作情况撷取下来 实例: [root@linux ~]# ps aux [root@linux ~]# ps -lA [root@linux ...

  9. js常用函数(不断添加中。。。)

    /************************************************* Function: getColor Description: 根据输入的数字返回一个颜色值 In ...

  10. Android:requestWindowFeature应用程序窗体显示状态操作

    注意requestWindowFeature必须在 setContentView()之前调用. 1.DEFAULT_FEATURES:系统默认状态,一般不需要指定 2.FEATURE_CONTEXT_ ...