事件驱动模型

目录

1 概念

2 作用

3 实现思路

3.1 三要素

3.2 三要素之间的关系

3.3 常见的实现形式

3.4 同步驱动和异步驱动

4 经典实现

5 示例(点击链接查看)


1 概念

在一些应用场景中,我们希望程序是被“事件”触发运行的,并且程序在感知到不同的“事件”后能够产生不同的响应动作(例如常见的UI应用程序,在用户点击不同的按钮后会产生不同的动作效果),此时就需要应用程序能够实时“感知”其所关心的事件,并在事件发生后执行相应的操作。

在解决上述问题时,应用程序是由“事件”驱动运行的,这类程序在编写时往往可以采用相同的模型实现,我们可以将这种编程模型称为事件驱动模型

个人理解:事件驱动模型其实是一种抽象模型,用于对由外部事件驱动系统业务逻辑这类应用程序进行建模。

2 作用

基于事件驱动的应用程序可以实时响应所关心的事件,实现实时检测、响应外部动作,这是事件驱动模型的基本功能和作用。在一些复杂的系统中,事件驱动模型还可很好地发挥以下作用:实现组件之间的松耦合/解耦实现异步任务跟踪状态变化

  • 实现组件之间的解耦

在复杂系统中,往往存在多个组件相互耦合的情况,如果将组件之间的耦合关系抽象成“事件(event)”,让事件担任组件之间的通信任务,就能而降低\解除组件之间的耦合关系。

参考以下示例

//示例1    经典实现,类A、B之间的耦合关系较强耦合
class A {
    void act1() {}
    void act2() {}
    //...
}

class B {
    A a;
    void action() {
        //TODO 根据业务逻辑判断需要触发a的哪个动作
    }
    //....
}

示例1的代码段中,类B对类A有较强的依赖,B需要根据不同的业务逻辑调用A的不同动作,若类A的实现有变化那么类B难免需要进行适配。如果将A、B之间的耦合关系抽象为事件,代码见示例2

//示例2    将A、B的耦合关系抽象为事件,以实现解耦
class Event {
    //...
}

class A {
    void notify(Event e) {
        //TODO 判断事件类型,触发响应动作
    }
    //...
}

class B {
    A a;
    void action() {
        Event e = craeteEvent();
        a.notify(e);
    }

    Event createEvent() {
        Event e = new Event();
        //...
        return e;
    }
    //...
}

此时B将更多的关注点转移到了Event,与A的关系仅限于B通知A需要处理业务,具体触发什么动作由A自己判断并触发。相比于示例1的实现,示例2的实现中引入了类Event辅助类A、B之间通信,类A与类B之间的耦合关系也更松弛。

个人理解:事件驱动模型,实际上是将组件之间的耦合关系转移到了“事件(Event)”上,但是对于某个领域而言事件(Event)一般具有通用性并且不会频繁变更实现逻辑,所以事件驱动模型可以很好地实现组件之间的解耦。

  • 实现异步任务

在一些业务场景中,顺序、阻塞式地执行任务会遇到一些比较耗时的中间步骤,但是不希望整个流程都停下来等待这些中间步骤完成,而是触发一个异步操作然后继续执行当前任务,在收到异步操作处理完成的消息之后再执行相关的处理。

打一个比较容易理解的比方:上课的时候需要记学习笔记,但是笔记内容较多的时候,老师不会停下来等大家做完笔记再继续讲课,大家只能先记住“记笔记”这件事,等老师讲完课后在把笔记补上,有时候我们补完笔记还需要告诉老师“笔记已经做好了”,请老师检查笔记。

当然,也可以是多线程下的异步任务:如果有的同学具备一心二用的能力,他就可以在听课的同时做完笔记,然后告诉老师他已经完成了“记笔记”的任务。

在一些事件驱动模型的设计结构中,会存在“任务队列”用以存储需要处理的“事件(Event)”,这种结构的事件驱动模型就可以很好地实现上述业务场景。

使用事件驱动模型实现异步任务的一般思路是:当遇到耗时较大、没有同步执行要求的操作时,针对这个操作触发一个事件,将这个事件加入到任务队列中,直到有一个进程(线程)能够获取并执行这个任务,才开始执行这个任务。

  • 跟踪状态变化

在存储实体模型的业务中通常需要修改实体模型的数据,对于部分业务场景需要存储、使用实体模型的历史变更记录,例如什么时间对实体数据做了什么修改。对于这类需求,事件驱动模型也可以提供很好的解决方案,我们可以认为每次修改实体数据都是一次事件,那么在修改实体数据后将这个事件存储到事件队列中即可实现跟踪状态变化的需求。

待补充...

对于事件驱动模型作用的的理解,可以参考:事件驱动架构设计

3 实现思路

事件驱动模型有很多种体现形式,如简单的事件触发机制、单线程异步任务、多线程异步任务等,但是各种技术中实现事件驱动模型的基本思路相同。

事件驱动模型的基本结构如图1所示

图 1 事件驱动模型基本结构

事件驱动模型包括三个基本要素:事件状态对象(Event)事件源(EventSource)事件监听器(EventListener)

3.1 三要素

  • 事件状态对象(Event)

Event作为事件的抽象,对事件本身进行了描述,携带着事件的详细信息(如在UI系统中,Event携带的信息有鼠标动作类型、鼠标坐标等信息)。在事件驱动模式中Event起着传递事件信息的作用,负责把被触发事件的详细信息传递给EventListener,以便EventListener作出正确的响应动作。

  • 事件源(EventSource)

EventSource主要用于感知外部事件,将事件打包成Event并分发给EventListener处理。

个人理解:这里的EventSource实际上担任了两个职责:1)生成事件;2)派发事件。依据职责单一原则的描述EventSource应该只担任“生成事件”的职责,而“派发事件”的职责应当重新设计一个EventDispatcher类来担任,但是由于单应用场景下“派发事件”的业务逻辑通常比较简单,所以这里将EventDispatcher的职责也交由EventSource负责。

PS:到底应该设计成EventSource还是 EventSource EventDispatcher ,涉及到事件驱动模型的两种实现形式:以观察者模式实现、以发布订阅模式实现,3.3节会具体谈到。

  • 监听器(EventListener)

EventListener封装了响应事件的能力,它职责就是在感知到EventSource触发Event后,处理Event对象以响应事件。一般需要在EventSource预留注册EventListener的接口,以便在运行时向EventSource添加处理事件的监听器。

3.2 三要素之间的关系

关系对关系
EventSource -- EventListenerEventSource注册EventListener
EventSource通知EventListener处理Event
Event -- EventSourceEventSource生成Event,并派发Event
Event -- EventListenerEventListener消费Event

3.3 常见的实现形式

事件驱动模型有两种实现形式:以观察者模式实现以发布/订阅模式实现。下面先介绍一下关于观察者模式发布/订阅模式的区别。详细内容可以参考发布订阅模式与观察者模式

观察者模式订阅/发布模式的主要区别在于两者触发事件的机制不同,如图2所示(借同业绘制的对比图一用,侵删)

图片描述
图 2 观察者模式与发布/订阅模式对比图

上图是为了对比观察者模式订阅/发布模式两者之间的区别,所以图中各模块的名字遵循了规范的写法。本文的目的在于对比两个模式在实现事件驱动模型时的区别,为了方便后文描述,将上图各的名字作如下映射:

  • EventSource:表示SubjectPublisher

SubjectPublisher在模型中担任的主要职责都是生成EventSubject还担任了简单的分发Event的职责)。

  • EventDispatcher:表示Topic/EventChannel

图中的“Topic/EventChannel”3.1节讨论的EventDispatcher(调度器)担任的职责相同(调度Event),本人更倾向于使用“调度器”来描述这个模块l

  • EventListener:表示ObserverSubscriber

ObserverSubscriber在模型中担任的职责都是监听、处理Event

图2可见,订阅/发布模式直观上来看比观察者模式多了一个EventDispatcher模块,并且EventListener的依赖关系也从EventSource转移到了EventDispatcher上。观察者模式订阅/发布模式存在这种直观上的差异,是因为两者解决问题的场景不同:

  • 观察者模式关注的是事件发生后观察者能够获取到足够的数据。这种场景下Event种类单一,但是Event会被多个EventListener关注,Event中携带的信息更多的是观察者需要的数据。这种场景下,要求事件发生后EventSource能够通知所有EventListener,此时调度Event的逻辑很简单,不必设置独立的调度器执行调度任务。

PS:如果需要实现异步任务,EventSource中会设计一个事件队列用于异步调度

  • 订阅/发布模式关注的是事件发生后能够准确地触发相应的动作。这种场景下Event种类多,每类Event只有多个EventListener关注,Event中携带的多是事件自身的一些信息(例如EventID之类),而很少携带其他数据。这种场景下,Event更像一个触发信号,不同的Event会触发不同的EventListener执行动作,此时调度Event的逻辑复杂,需要设置独立的调度器执行调度任务。

PS:如果需要实现异步任务,EventDispatcher中会设计一个事件队列用于异步调度

观察者模式订阅/发布模式的具体差异对比见下表

观察者模式发布/订阅模式

EventSource与EventListener

的耦合关系

松耦合完全解耦
角色(除Event外)

2个

EventSource、EventListener

3个

EventSource、EventDispatcher、EventListener

EventSource个数一个可能存在多个
事件种类数一种多种
EventListener种类数多种多种
消息中间件不存在存在:EventDispatcher
事件与监听器对应关系一个事件对应多个监听器一个事件对应多个监听器
Event携带的内容数据一般不携带除描述Event自身信息以外的数据
应用场景独立应用程序多组件的应用程序、夸应用的系统

在具体的业务中是选择观察者模式还是发布/订阅模式来实现事件驱动模型,取决于具体的业务场景和对Event的抽象粒度。如果系统中只有一种Event的抽象,优先考虑使用观察者模式实现;如果系统中存在多种Event抽象,优先考虑发布/订阅模式实现。

对于Event该如何抽象也因人而异。例如在《HeadFirst Pattern》一书讲解观察者模式的那个例子,气象站检测到的数据有温度、气压、湿度等,如何对事件进行抽象就有两种思维:

1)认为事件就是气象数据变化,无论温度、气压还是湿度的一项或者多项有变化,都触发同一个事件。

2)认为温度变化、气压变化、湿度变化是三种不同的事件,当其中的一项数据变化时触发相应的事件,如果三项数据同时变化将会触发三个事件。

对于1)中的抽象结果,可能使用观察者模式更合适;而对于2)中的抽象结果,则使用发布/订阅模式更合适。可见对于相同的应用场景,不同的抽象方式会有不同的使用情景。

PS:经过对观察者模式发布/订阅模式的对比,容易发现本文之前举的例子(示例2图2)其实都是以观察者模式实现的。

个人理解观察者模式发布/订阅模式都可以实现事件驱动模型。在实现事件驱动模型时可以选用观察者模式发布/订阅模式中更适合当前业务场景的模式组织编码,具体如何实现可以参考观察者模式发布/订阅模式的资料,也可以参考后文给出的案例链接。

3.4 同步驱动和异步驱动

在第2节中我们已经了解了异步任务的含义,也对异步任务的实现思路做了简单介绍。相比于同步任务异步任务的优势在于处理耗时长的任务时不会阻塞主线任务。

大家在比较同步任务异步任务时,一般还会和多线程任务一起进行对比,本文不对这三种时序模式作太详细的介绍,这里借用一张比较经典的对比图(如图3)来解释一下三者的区别,如大家想了解更多可以查阅同步、异步、多线程等相关知识。

图 3 同步、异步、多线程对比
  • 单线程同步模型:三个任务依次顺序执行,并且只有当某个任务完全执行完成后才能执行下一项任务,如果这三个任务之间不存在依赖关系,但是任务中存在阻塞操作(如IO操作),程序将会存在大量的等待时间,导致程序的运行速度变慢。
  • 多线程模型:三个任务运行在不同的线程中,任务之间不会彼此影响。这种模型往往需要考虑多线程同步的问题,处理起来难度相对较大,并且对于每个任务而言线程仍然会存在等待时间,只是缩短了整个程序的等待时间、提高了CUP利用率。实现方式参考多线程相关知识。
  • 异步模型:三个任务仍然在同一个线程中执行,只是在当前任务遇到阻塞操作时会让出资源,让其他任务先执行,如此交替执行。这种模式在遇到阻塞操作时不会等待,所以程序运行速度会快很多。异步模型的实现思路可以参考第2节的描述,需要维护一个任务队列,当遇到阻塞操作时将任务放到队列中,阻塞操作完成后将任务从队列中取出等待执行机会,在获得执行机会后执行回调函数继续处理任务。

PS:这三种模型没有好坏之分,在开发时选用哪种模型取决于具体的业务场景。)

经过上述比较,我们可以清晰地认识到同步模型异步模型在实现上的区别,异步模型需要借助任务/事件队列实现。

在事件驱动模型中,驱动事件的动作可以选择同步驱动异步驱动

同步驱动事件:生成事件、触发事件和处理事件是顺序执行的。EventSource在生成Event后立即通知EventListener,事件处理完成后返回处理结果。具体实现参考观察者模式的经典实现。

异步驱动事件:生成事件、触发事件和处理事件不是顺序执行的。EventSource在生成Event后将Event缓存在事件队列中,然后通知EventListener,事件处理完成后如果有必要再调用回调函数上报处理结果。有时我们会采用多线程的方式,由线程A向队列缓存Event由线程B消费Event

基于异步驱动实现事件驱动模型时,还需要参考消息队列和生产者-消费者模式相关知识来维护事件队列。

4 经典实现

观察者模式

订阅/发布模式

UI框架

Netty

Spring 事件模型

Node.js

5 示例(点击链接查看)

Java事件驱动模型可以理解为一种异步编程机制,其核心在于监听事件并在事件发生时触发相应的处理逻辑。这种模型适用于需要高并发处理的场景,如网络编程、GUI编程等。 在Java中,事件驱动模型通常采用观察者模式实现。观察者模式是指当一个对象发生状态变化时,会通知其他观察者对象,让它们进行相应的处理。在事件驱动模型中,事件源对象会发出事件,观察者对象会监听事件并进行处理。 Java中的事件驱动模型通常包含以下几个组件: 1. 事件源(Event Source):事件源是指触发事件的对象,通常是某个组件或者方法。 2. 事件(Event):事件是指某个状态的变化或者某个动作的完成。事件通常包含事件源对象和事件类型。 3. 监听器(Listener):监听器是指负责监听事件的对象。在Java中,监听器通常实现了某个接口,如ActionListener、MouseListener等。 4. 事件处理器(Event Handler):事件处理器是指负责处理事件的对象。在Java中,事件处理器通常是监听器对象的一个方法,如actionPerformed()、mouseClicked()等。 Java中的事件驱动模型可以应用于多个场景,如GUI编程、网络编程、消息队列等。在实际应用中,需要根据具体的需求和场景,选择合适的事件驱动框架和技术,如Java AWT、Java Swing、Java NIO等。同时,需要注意事件驱动模型的线程安全问题,避免多线程并发访问导致的数据不一致或者死锁等问题。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值