工具与资源中心帮助开发者更高效地工作,提供围绕开发者全生命周期的工具与资源https://developer.aliyun.com/..1.从“开始”红灯停,绿灯行”在汽车行业,无论你是风驰电掣的车神,还是新马路杀手,都需要遵守这样一条红绿灯前的铁律——“红灯停,绿灯走。”从你坐上驾驶座的那一刻起,你就注定要追随“光”。在上面的场景中,有两个角色——红绿灯和司机。司机需要观察红绿灯的颜色变化(即变红或变绿),并根据不同的颜色变化做出相应的驾驶措施(即走或停)。).这种对象间的行为模式在软件设计中也存在,也就是我们下面要学习的设计模式——观察者模式。二、基本概念1、定义观察者模式(ObserverPattern)是一种对象行为设计模式,用于建立对象与对象之间的依赖关系。它的定义是:定义对象之间一对多的依赖关系,当一个对象改变状态时,自动通知所有依赖的对象。在这个定义中定义了两个对象:?目标对象:被依赖或观察的对象。当状态改变时,所有的观察者对象都会被通知。在上面的例子中,红绿灯是观察对象;?观察者对象:依赖对象,当被观察对象的状态发生变化时,会自动收到通知,并采取相应的动作(或执行状态对应的更新操作)。在上面的例子中,司机是观察者;其结构图如下:另外,观察者模式也称为发布-订阅模式、模型-视图模式、源-监听模式(Source-ListenerPattern)等。2.基于观察者模式的事件驱动模型在实际编程过程中,我们更关注某个事件的发生,比如上面提到的红绿灯变红/绿的事件,但是当交通灯亮后改变颜色,汽车会做出相应的动作(停止/启动)。这就是事件驱动模型,也称为委托事件模型(DelegationEventModel,DEM)。事件驱动模型中有以下三个要素:?事件源:事件最初发生的对象,在观察者模式下也对应被观察的目标对象。?事件对象:被触发的事件,事件对象需要能够执行事件的主体是事件源;?事件监听器:监听事件发生的对象。当被监听的对应对象发生事件时,事件监听器会根据发生的事件采取预设的相应动作;上面说的事件驱动模型其实是通过观察者模式实现的。下面是观察者模式和事件驱动模型的对应关系:对于观察者模式下的观察者对象,事件源和事件共同构成被观察和处理的目标对象,其中事件源对应被观察的目标对象(即事件监听器会注册到事件源上),事件源上发生的事件就是事件监听器需要处理的对象。发生在事件源上的事件实际上是观察者模式中目标对象状态改变动作的延伸。单一的状态变化不能更好地满足开发的需要,而事件具有更好的扩展性。三、源码探索1、JDK中的观察者模式观察者模式的使用非常普遍,JDK从1.0版本开始就提供了对该模式的支持。JDK中提供了Observable类和Observer接口。前者提供被观察对象的基类实现,后者提供观察者的通用处理接口。通过继承/实现这两个类,开发就可以轻松完成观察者模式的使用。下面详细分析一下Obserable类中的notifyObservers(Objectarg)方法:publicvoidnotifyObservers(Objectarg){2.//用于存储观察者集合的局部变量3.Object[]arrLocal;4.//Targethere对象被锁定,以防止在获取目标对象状态和观察者集合时出现线程安全问题。5.//但是通知观察者进行相应处理时,不需要保证线程安全。6.//在当前的竞争情况下,最坏的结果如下:7.//1)新加入的观察者将错过本地通知;8.//2)最近未注册的观察者会被错误地通知9.synchronized(this){10.//判断当前目标对象的状态是否发生变化11.if(!changed)12.return;13.arrLocal=obs.toArray();14.//清除状态15.clearChanged();16.}17.for(inti=arrLocal.length-1;i>=0;i--)18.//通知所有观察者执行相应的操作19.((Observer)arrLocal[i]).update(this,arg);}从这个方法可以看出,要完成对所有观察者的通知,必须满足目标对象状态改变的必要条件。为了保证获取状态和观察者集合时的线程安全,这里使用了synchronized关键字和局部变量。但是同步代码块中没有调用观察者update方法,可能会导致部分观察者收不到通知或者收到错误的通知。对于JDK提供的观察者模式,使用的流程是:Observable.setChanged()->Observable.notifyObservers(Objectarg)。2.JDK中的事件驱动模型除了观察者模式,JDK还实现了对事件驱动模型的支持。为此,JDK提供了EventObject类和EventListener接口来支持这种模型。前者代表事件驱动模型中的事件对象,后者代表事件监听器。首先我们看一下EventObject的构造函数:publicEventObject(Objectsource){2.if(source==null)3.thrownewIllegalArgumentException("nullsource");4.this.source=source;}可以看到,构造函数中必须传入一个source对象,在官方评论中定义为事件最初发生的对象。这个解释乍一看还是有点抽象,结合上面红绿灯的例子可能会更好理解。在红绿灯的例子中,红绿灯是事件源,红绿灯变色是事件,司机是事件监听者。司机作为事件监听器,实际观察交通灯。当发生红绿灯变色事件时,驱动程序会根据红绿灯变色事件进行相应的处理(即事件处理)。根据上面的逻辑不难看出,司机的事件监听器其实是注册到红绿灯的事件源,然后处理红绿灯上发生的事件。这里我们可以看一下JDK提供的事件监听接口EventListener。我们可以看到这里只声明了一个接口,里面并没有任何方法。从个人角度来说,这可能是因为笔者认为大家很难达成一致。与其想一个通用的方法,还不如简单定义一个接口,让用户自由发挥。3.Spring中的事件驱动模型——发布/订阅模式Spring框架进一步明确了事件驱动模型的数据模型,在原有的概念上增加了事件发布者的角色,从而得到了一个新的模型——发布/订阅模型。Spring框架在JDK的基础上,提供了ApplicationEvent、ApplicationListener和ApplicationEventPublisher三个基本类来支持发布/订阅模型。其中,ApplicationEvent和ApplicationListener分别继承了EventObject和EventListener,其功能与这两个类相同,这里不再赘述。这里特别注意新引入的类ApplicationEventPublisher。这个新引入的类对应了上面事件驱动模型中事件源的作用。这里有两个方法:@FunctionalInterfacepublicinterfaceApplicationEventPublisher{/**通知所有注册到发布者的监听器去处理相应的事件*@paramevent用于发布事件,这里的事件对象必须是ApplicationEvent的基类*/defaultvoidpublishEvent(ApplicationEventevent){publishEvent((Object)event);}/**通知所有注册到发布者的监听器去处理相应的事件@paramevent用于发布事件,任何类型的事件都可以使用Process*/voidpublishEvent(Objectevent);}可以看到,为了保证可扩展性和移动的自由度,Spring不仅提供了基于ApplicationEvent类型的事件发布方式,还提供了Object类型的事件处理。这里我们选择ApplicationEvent的基类AbstractApplicationContext,看看Spring中事件发布的逻辑:@OverridepublicvoidpublishEvent(ApplicationEventevent){publishEvent(event,null);}protectedvoidpublishEvent(Objectevent,@NullableResolvableTypeeventType){Assert.notNull(event,"Eventmustnotbenull");//将事件包装成ApplicationEventApplicationEventapplicationEvent;if(eventinstanceofApplicationEvent){applicationEvent=(ApplicationEvent)event;}else{applicationEvent=newPayloadApplicationEvent<>(this,event);if(eventType==null){eventType=((PayloadApplicationEvent>)applicationEvent).getResolvableType();}}//如果可能,立即多播//或在多播器初始化后延迟多播Multicastif(this.earlyApplicationEvents!=null){this.earlyApplicationEvents.add(applicationEvent);}else{//广播事件,这里是广播的关键getApplicationEventMulticaster().multicastEvent(applicationEvent,eventType);}//通过上下文发布事件父类的if(this.parent!=null){if(this.parentinstanceofAbstractApplicationContext){((AbstractApplicationContext)this.parent).publishEvent(event,eventType);}else{this.parent.publishEvent(event);}}}/**广播事件给对应的监听器*/publicvoidmulticastEvent(finalApplicationEventevent,@NullableResolvableTypeeventType){ResolvableTypetype=(eventType!=null?eventType:resolveDefaultEventType(event));Executorexecutor=getTaskExecutor();for(ApplicationListener>listener:getApplicationListeners(event,type)){if(executor!=null){executor.execute(()->invokeListener(listener,event));}else{invokeListener(listener,event);}}}除了事件准备的过程,将事件广播给对应的监听器,然后调用监听器对应的方法。这个过程和上面看到的Observable通知监听方法基本一样,只是和JDK中的同步处理不同。如果Spring中有事件处理的线程池,也可以使用线程池。异步处理相应的事件,进一步解耦发布者和监听者。4.总结Observer模式最大的特殊性就是建立一对多、松耦合的关系。观察目标只需要维护一个抽象的观察者集合,不需要去感知具体的观察者。这样的松耦合关系有利于观察目标和观察者进行相应的抽象处理,很好地体现了开闭原则。当然观察者模式也有缺点,比如只能定义一对多的关系,不能处理多对多的场景;又如,它只能感知观察目标的变化,却无法理解变化是如何发生的,等等。这些都是观察者模式无法处理的场景或问题。本文转载:https://developer.aliyun.com/...
