本文分为三个部分:
 
  • 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. linux -- 启动时启动服务或者执行命令

    运行等级 首先,我们需要知道Linux系统关于运行等级的知识.在不同的linux系统上(例如ubuntu和Fedora)这些数字与和所代表的意义可能不同,但主要的有以下几个: 单用户模式. 多用户模式 ...

  2. ubuntu find方法

    通用格式:find pathname -options [-print -exec -ok]例子:find / -name filename 再根目录里面搜索文件名为filename的文件find / ...

  3. 搭建Git Server

    windows上如何搭建Git Server   Git在版本控制方面,相比与SVN有更多的灵活性,对于开源的项目,我们可以托管到Github上面,非常方便,但是闭源的项目就会收取昂贵的费用.那么私有 ...

  4. 用于软件包管理的21个Linux YUM命令

    FROM:http://os.51cto.com/art/201309/411895.htm YUM(Yellowdog Updater Modified)是一款开源命令行及图形化软件包管理工具,面向 ...

  5. 1020: [SHOI2008]安全的航线flight - BZOJ

    Description在设计航线的时候,安全是一个很重要的问题.首先,最重要的是应采取一切措施确保飞行不会发生任何事故,但同时也需要做好最坏的打算,一旦事故发生,就要确保乘客有尽量高的生还几率.当飞机 ...

  6. js检测对象的类型

    在JavaScript中,想要判断某个对象值属于哪种内置类型,最靠谱的做法就是通过Object.prototype.toString方法. 示例: var array=[1,2,3]; Object. ...

  7. window.location.hash属性介绍

    location是javascript里边管理地址栏的内置对象,比如location.href就管理页面的url,用location.href=url就可以直接将页面重定向url.而location. ...

  8. encodeURIComponent()编码和decodeURIComponent()解码

    html1: <!DOCTYPE HTML> <meta charset=utf-8> <meta http-equiv="X-UA-Compatible&qu ...

  9. Django 后台搭建

    # Django settings for gameadmin project. DEBUG = True TEMPLATE_DEBUG = DEBUG ADMINS = ( # ('Your Nam ...

  10. 【BZOJ 2453|bzoj 2120】 2453: 维护队列 (分块+二分)

    2453: 维护队列 Description 你小时候玩过弹珠吗? 小朋友A有一些弹珠,A喜欢把它们排成队列,从左到右编号为1到N.为了整个队列鲜艳美观,小朋友想知道某一段连续弹珠中,不同颜色的弹珠有 ...