设计模式:Observer(观察者)—— Guava EventBus

本文分为三个部分:

  • 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

时间: 2024-08-06 08:34:26

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

Design Pattern - Observer 观察者设计模式

Spy on enemy. 使用这个模式可以根据某些事件自动更新. 设计思路: 1 设计一个基类,作为需要观察一个时间行为的接口类 2 设计一个观察者类,可以观察所有基类的衍生类, 这里使用set来保存这些需要更新的类. 一个事件相当于一个函数,事件发生(调用函数)同时自动调用需要更新的函数动作. #pragma once #include <iostream> #include <string> #include <set> #include <vector&g

Java设计模式(五) Observer(观察者)模式及Tomcat引申

基本概念 Observer 观察者模式(Observer Pattern)又称为发布订阅模式(Publish/subscribe) 定义对象间的一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并且自动更新 根据单一职责原则,每个类的职责是单一的,我们可以通过触发机制,形成一个触发链,把各个单一的职责串联成真实世界中的复杂的逻辑关系. 观察者模式的角色分工(JDK中提供了抽象接口的定义): Subject被观察者: 抽象类型,定义被观察者必须实现的职责,动态地增加和

guava eventbus代码分析

分析guava eventbus之前,先看一下传统观察者模式的写法: Subject接口是抽象主题,相当于被观察者,它持有一个监听者observer的列表,attach方法往这个列表里面注册监听者,detach方法注销监听者,notify方法用于事件发生时通知到列表中的监听者 通常在notify的实现方法中会调用监听者的update方法. Observer是抽象观察者,带一个update方法,update方法被具体主题的notify方法调用.

C++设计模式实现--观察者

概述 在软件构建过程中,我们需要为某些对象建立一种"通知依赖关系" --一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知.如果这样的依赖关系过于紧密,将使软件不能很好地抵御变化.使用面向对象技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系.从而实现软件体系结构的松耦合.     意图 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新.[GOF <设计模式>]  特点: 1.  Subj

Guava库学习:学习Guava EventBus(二)EventBus 事件订阅示例

原文地址:Guava库学习:学习Guava EventBus(二)EventBus 事件订阅示例 上一篇Guava库学习:学习Guava EventBus(一)EventBus,我们简单的对Guava基于事件的编程进行了介绍,学习和了解了EventBus类的使用,本篇起,我们通过一系列的示例代码深入的学习EventBus类,本篇学习Guava EventBus(二)EventBus 事件订阅示例.     订阅Subscribe 首先,我们假定定义了如下所示的TradeAccountEvent类

[Guava] EventBus

1.  发布-订阅模式 发布-订阅模式(publish-subscribe)是一种编程范式,发布方不发布消息给特定的接收方,而是由订阅方选择性接收.这使得发布方和订阅方相对独立,减少了耦合性. 在发布-订阅模式中,有以下几个难点: 1)如何区分或分配订阅者关注的消息: 2)发布者如何将消息提交给对应订阅者: 下图描述Guava EventBus对发布-订阅模式的实现. 2. 订阅者注册 下面是简单的订阅者实现: // 订阅者 public class Subscriber { @Subscrib

Guava库学习:学习Guava EventBus(一)EventBus

原文地址:http://www.xx566.com/detail/184.html 在软件开发过程中,对象信息的分享以及相互直接的协作是必须的,困难在于确保对象之间的沟通是有效完成的,而不是拥有成本高度耦合的组件.当对象对其他组件的责任有太多的细节时,它被认为是高度耦合的.当一个应用程序有高度的耦合,维护将变得非常具有挑战,任何变化都将带来涟漪效应.为了解决这一类的软件设计问题,我们就需要基于事件的编程.本篇,我们就来学习Guava 基于事件的编程,Guava EventBus(一)EventB

Guava - EventBus(事件总线)

Guava在guava-libraries中为我们提供了事件总线EventBus库,它是事件发布订阅模式的实现,让我们能在领域驱动设计(DDD)中以事件的弱引用本质对我们的模块和领域边界很好的解耦设计. 不再多的废话,直奔Guava EventBus主题.首先Guava为我们提供了同步事件EventBus和异步实现AsyncEventBus两个事件总线,他们都不是单例的,官方理由是并不想我们我们的使用方式.当然如果我们想其为单例,我们可以很容易封装它,一个单例模式保证只创建一个实例就对了. 下面

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

观察者设计模式:时当一个对象发生指定的动作时,要通过另外的对象做出相应的处理. 步骤: 1. A对象发生指定的动作是,要通知B,C,D...对象做出相应的处理,这时候应该把B,C,D...对象针对A对象的动作做出的相应处理方法定义在接口上(这是一种规范,凡事需要A对象通知的对象,都要实现该接口). 2. 在A对象维护接口的引用,当A对象发生指定的动作这时候即可调用接口中的方法. 观察者模式的应用场景: 1. 对一个对象状态的更新,需要其他对象同步更新,而且其他对象的数量动态可变. 2.对象仅需要