从观察者模式到 Java 事件处理机制(上)
一、前言
事件、事件监听,这样的词在我们日常工作中并不少见,尤其是在一些架构设计、中间件源码中,相关的概念、代码更是屡见不鲜,可见其在编程世界里扮演着举足轻重的角色。然而,有些同学在实际应用事件相关的理念时,仍然可能有些困难,本文将逐步地说明事件的相关概念与应用实践,以便于深入理解事件。
事件模型,通俗来说是一种设计思想,是对实际应用场景的一种抽象模型设计(有的地方,也叫事件驱动模型、事件模式、事件机制等,通常指的是同一个意思)。在细说事件模型之前,我们先说一下观察者模式,Java 的事件处理机制,在一定程度上来说,就是是观察者模式的一种变种或者说升级实现,而观察者模式相对更好理解一些,在理解了观察者模式之后,再理解事件模型就水到渠成了。
二、观察者模式
2.1、基本概念
观察者模式也是一种编程设计思想,是经典的 23 种设计模式之一,其大致定义是:当对象之间存在一种一对多的依赖关系时,每当被依赖对象的状态(行为)发生变化时,所有依赖于它的对象都会得到通知并自动更新(进行自己相应的行为操作)。其主要应对的场景是,一个对象的状态改变给其它依赖对象通知的问题,并且要相对优雅地处理这个问题(代码低耦合、易扩展等)。
生活中,适用于观察者模式的场景很多,比方说:过马路的人们观察到红绿灯变绿了,人们开始过马路;跑步运动员观察到裁判员起跑发令枪响了,开始跑步;球迷观察到球员进球了,开始欢呼。类似这种一对多的依赖关系,实际生活场景之中比比皆是。
大家注意到没有,以上的例子中,对象关系之间有两个主要的角色,分别是被观察者、观察者,而被观察者虽然有个"被"字,但其实是掌握着整个事情行为的主动权的。比方说,是红绿灯主动变绿了,人们才能观察到,然后过马路的;是裁判主动打响了发令枪,运动员才听到枪响开始跑步的,理解这个主动/被动关系非常重要。
我们用裁判员、运动员的例子来分析一下整个过程当中的的各角色行为。作为观察者的运动员,只要做一件事情,那就是观察到裁判员的发令枪响后开始跑(也就是听到发令枪响,收到起跑通知);而作为被观察者的裁判员则需做两件事情,一是打响发令枪,二是打响发令枪后,通知所有参赛运动员开始跑(发令枪响了,就是通知)。当然,作为裁判员,他需要知道到底要通知哪些参赛运动员,所以在裁判员对象中,应当包含需要通知的运动员列表,并且可以新增需要通知的运动员、删除不再需要通知的运动员。
2.2、应用示例
程序是生活的抽象实现,通过上面的初步分析,我们可以对裁判员、运动员的例子进行相关的接口/类设计如下图所示:
简单说明一下设计图中的接口/类:
AbsObservable
被观察者抽象类;维护一个观察者列表,抽象定义增加观察者、删除观察者、通知观察者的公共方法。
IObserver
观察者接口;抽象定义观察者被通知后的行为方法。
Referee
裁判员对象;具体的被观察者,继承被观察者抽象类,同时定义了自己的业务逻辑方法。
Sportsman
运动员对象;具体的观察者,实现观察者接口,并在实现方法中完成自己特定的业务逻辑。
接下来,我们看一下这个场景的具体代码实现。
1、被观察者抽象类AbsObservable
:
2、观察者接口IObserver
:
3、具体被观察者 裁判员对象Referee
:
4、具体观察者 运动员对象 Sportsman
:
接下来,我们模拟一下裁判员打响发令枪,运动员观察到后开始跑的调用过程:
执行后输出:
以上就是观察者模式应用的一个基础版的演示,大家可以回过头来看一看、想一想,是不是还是比较好理解的。当然,实际工作中的应用可能会比这个示例更复杂一些,但核心设计思想基本是一样的。
贴心的JDK
提供了观察者、被观察者两个角色的抽象接口/实现类(本文基于 JDK1.8),分别是java.util.Observer
接口、java.util.Observable
类。大家可以看一下这两个类的源代码,会发现和笔者自定义的非常相似,在实际应用中,通常情况下,就不用自己抽象定义了,当需要时,具体观察者直接实现java.util.Observer
接口,具体被观察者直接继承java.util.Observable
类就可以了。(本文的演示代码都是基于 Java 的,但实际上不论什么开发语言,思想是通用的,所以,理解了设计思想对于我们来说基本就一通百通了。)
2.3、优点缺点
观察者模式作为一种设计思想,有其优秀的地方,自然也会有所不足,在实际应用场景中,通常会比笔者演示的示例要更复杂一些,需要考虑的问题也就会更多一些。
此处对观察者主要的优缺点进行简单说明:
优点:
1、观察者、被观察者之间是上层抽象耦合的,有利于代码的扩展;(比方说:如果需要一个监督员来监督(观察)裁判员发出起跑命令的行为是否合规,只要定义一个监督员对象实现观察者接口,然后在实现方法中写自己的业务逻辑就可以了,非常地方便。)
2、构建了一套通知机制,有利于提高代码逻辑的清晰度;(比方说:运动员观察到裁判员发令枪响后开始跑,现场观众观察到运动员开始跑后,就开始呐喊加油,摄影师观察到观众呐喊后,开始抓拍照片。这是一系列的多级依赖关系,如果不使用观察者模式的话,程序代码要表达这一行为过程会很繁杂,而使用观察者模式,程序代码就可以更清晰地描述这个过程。)
3、提升了程序代码的规范性、可重用性。(对象的公共行为、依赖关系都抽象到了上层接口/抽象类中)
缺点:
1、可能会引发性能问题。因为被观察者通知所有观察者时,是逐个按顺序同步通知的(由 JDK 源码可知,是 for 循环调用通知),所以如果观察者较多的话,通知完成可能时间较长,从而被观察者需要等待较久,可能出现“卡顿”的现象,如果是多级观察(依赖)的话(或者不小心引发循环依赖),这种风险就更大了,甚至可能直接就“卡死”了,所以在实际应用时,需要仔细考虑。当然,可以做一些变种优化,比方说"通知"改成异步的,或者控制/减少观察者的数量、层级,优化观察者自有业务的执行效率等。
2、可能会带来开发/调式的的困难。因为观察者模式可以有众多观察者,且可以多级依赖“观察”,从而形成一个类似“链式”调用,如果在设计/开发时,没有控制好的话,反而可能带来开发/调式的繁琐,稍微想想那种层层往返调试的场景都头大。所以,在使用观察者模式时,一定要设计规划好,否则,反而会带来反效果。(类似责任链模式、装饰模式基本都有类似的问题,实际应用时都不应该有太多层/依赖,否则后续维护会非常难受。)
tips:
观察者模式多级依赖下的“链式”调用,和责任链模式下的链式调用还是有所区别的,其中重要的区别在于:观察者模式的通知消息在传播链中,消息内容一般是变化的,是由当前节点对象、相邻下级节点对象“协商”决定通知什么消息内容给下一节点的(比方说,裁判员、运动员协商通知什么消息给运动员,运动员又该如何处理裁判员的通知消息;运动员、粉丝协商决定通知什么消息给粉丝,粉丝又该如何处理运动员的通知消息;裁判员、粉丝之间是没有直接关系的)。而在责任链模式下,消息内容自第一个节点开始逐级传播后,消息内容一般是不变的,链条中的每一个节点要处理的消息内容都是相同的(可能会在原始消息内容上略加补充),都是对同样的消息内容校验是否需要自己处理(如果能到当前节点的话)。
2.4、适用场景
由上文可知,观察者模式作为一种设计思想(具体实现可能略有差异,JDK 提供了一种实现),有其非常优秀的地方,也有所不足,通常来说,如果实际业务场景有以下特点的话,可以考虑使用。
1、对象/行为之间存在某种一对多的关联依赖关系,当一个对象发生变化时,需通知其它依赖对象,但并不需要其它对象的执行细节;
2、对象之间存在某种触发依赖链的关系,比方说,对象 A 会触发对象 B 的某种行为,对象 B 又会触发对象 C 的某种行为;
3、业务系统需要跨模块或者跨系统通知调用,希望使用消息通知机制,但是又不想引入较重的消息中间件,且对消息通知的可靠性要求不是特别高的情况下,可以考虑使用观察者模式这种相对轻量级的通知机制。(引入消息中间件,当然可以更好的应对消息通知的场景,但同时这也意味着更多的组件依赖,更多的资源消耗,更复杂的调用链路。)
tips:
观察者模式,有的地方也叫发布订阅模式,不过这两者之间还是有一些区别的,发布订阅模式应该说是观察者模式的一种变种。相对于观察者模式主要有观察者、被观察者这两个角色,发布订阅模式通常还有一个经纪人(Broker)的中间角色,通过这一角色的引入,优化了观察者模式的不足(发布者、订阅者之间不存在耦合),进一步降低了观察者(订阅者)、被观察者(发布者)之间的依赖关系,提高了整体的处理能力与可靠性。
实际业务需求场景中,在需要用到消息通知机制的时候,我们更多地会选择引入独立的消息中间件来解决问题,因为这样相对更简单、高效、可靠。但是,如果是我们自己需要做一个相对独立的中间件系统(或者技术框架)的话,更好的选择则是根据观察者模式/发布订阅模式的设计思想,自己设计实现相关需要的功能,因为这种情况下依赖独立的消息中间件的话,就显得太“笨重”了。
2.5、示例扩展
思考一下,对于裁判员、运动员的场景,如果还有一个新的对象粉丝,在运动员开始跑后,粉丝就开始呐喊加油,这时我们该如何处理呢?我们在以上示例的基础上稍加修改,就可以实现这个场景下的代码逻辑。
分析一下,这种情况下,我们一共有裁判员、运动员、粉丝这 3 个对象了,此时对象之间的关系如下图所示:
首先,裁判员对象作为被观察者,没有新的关系/行为产生,所以不需要做任何改变。
其次,我们需要新增一个粉丝对象,作为运动员对象的观察者。
Fans
对象代码如下:
再有,此时的运动员对象除了作为裁判员对象的观察者外,还成为了粉丝对象的被观察者,所以也需要实现AbsObservable
抽象类,此外,作为被观察者,在执行完成自己特定的业务逻辑后,需要“通知”它的所有观察者。
Sportsman
对象微调后的代码如下:
至此,我们就完成了新的场景下的代码扩展,是不是很简单!简单就对了,这正是观察者模式的魅力所在,下面我们模拟一下新的场景调用过程:
执行后输出:
注意看输出结果的输出顺序(执行顺序)!思考一下示例场景的扩展实现,再回过头来看一下前面所说的优缺点、适用场景,是不是对观察者模式有了更深一点的理解呢?
下篇请看 从观察者模式到Java事件处理机制(下)
越学越无知,我是老农小江,欢迎交流~
版权声明: 本文为 InfoQ 作者【老农小江】的原创文章。
原文链接:【http://xie.infoq.cn/article/c800208d95c4a55bad39b0ed5】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论