共计 5302 个字符,预计需要花费 14 分钟才能阅读完成。
工具与资源核心
帮忙开发者更加高效的工作,提供围绕开发者全生命周期的工具与资源
https://developer.aliyun.com/…
一、从“红灯停,绿灯行”开始
在汽车界,不管你是迅捷如风的秋名山车神,还是新上岗的马路杀手,在交通灯前都须要恪守这样一条铁律——“红灯停,绿灯行”。当你坐上驾驶位的那一刻,就注定了你必须随“灯”而行。
在下面的场景中呈现了两个角色—— 交通灯 和 驾驶员 ,驾驶员须要察看交通灯的变色状况(即 变红 或 变绿),依据不同的变色状况作出对应的行驶措施(即 行 或 停)。这一对象间的行为模式在软件设计中同样存在,也就是咱们上面要学习的设计模式—— 观察者模式 。
二、基本概念
1. 定义
观察者模式 (Observer Pattern)是用于建设一种对象和对象之间依赖关系的 对象行为型设计模式,其定义为:
在对象之间定义一个一对多的依赖,当一个对象状态扭转时,所有依赖的对象都会主动收到告诉。
在这一定义中明确了两个对象:
• 指标对象:即被依赖的对象或被察看的对象,当状态产生变更时会告诉所有的观察者对象。在下面的例子中,交通灯就是被察看的对象;
• 观察者对象:即依赖的对象,当察看的对象状态产生变更时会主动收到告诉,依据收到的告诉作出相应的行为(或进行对应状态的更新操作)。在下面的例子中,驾驶员就是其中的观察者;
其结构图如下:
除此以外,观察者模式 也被称为 公布订阅模式 (Publish-Subscribe Pattern)、 模型 - 视图模式 (Model-View Pattern)、 源 - 监听器模式 (Source-Listener Pattern)等等。
2. 基于观察者模式的事件驱动模型
在理论的编程过程中,咱们更多的是关注某一事件的产生,比方下面所说的 交通灯变红 / 变绿 这样一个事件,而在产生了交通灯变色之后,汽车才会做出相应的动作(停车 / 启动),这就是 事件驱动模型,也称委派事件模型(Delegation Event Model,DEM)。在事件驱动模型中有以下三个因素:
• 事件源:即最后产生事件的对象,也对应者观察者模式中被察看的指标对象;
• 事件对象:即被触发的事件,事件对象须要有可能执行该事件的主体,即事件源;
• 事件监听者:即监听产生事件的对象,当监听的对应对象产生某个事件之后,事件监听者会依据产生的事件做出事后设定好的相应动作;
上述所说的事件驱动模型其实是通过观察者模式来实现的,上面是观察者模式和事件驱动模型的对应关系:
从上图中能够看到,在事件驱动模型中,事件监听者就对应着观察者模式中的观察者对象,事件源和事件独特组成了被察看和被解决的指标对象,其中事件源对应着被察看的指标对象(即事件监听者会被注册到事件源上),而产生在事件源上的事件则是须要被事件监听者解决的对象。
产生在事件源上的事件实际上是对观察者模式中的指标对象的状态变更这一动作的扩大,繁多的状态变更无奈更好的满足开发的须要,而事件则具备更好的扩展性。
三、源码探索
1. JDK 中的观察者模式
观察者模式是如此的罕用,以至于 JDK 从 1.0 版本开始就提供了对该模式的反对。在 JDK 中提供了 Observable 类和 Observer 接口,前者提供了被察看对象的基类实现,后者则提供了观察者的通用解决接口。通过 继承 / 实现 这两个类,开发能够很轻松的实现观察者模式的应用。
上面具体分析一下 Obserable 类中的 notifyObservers(Object arg) 办法:
-
public void notifyObservers(Object arg) {
2. // 局部变量,用于寄存观察者汇合 3. Object[] arrLocal; 4. // 这里对指标对象加锁,避免获取指标对象状态和观察者汇合时呈现线程平安问题。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 (int i = arrLocal.length-1; i>=0; i--) 18. // 告诉所有观察者进行对应操作 19. ((Observer)arrLocal[i]).update(this, arg);
- }
从该办法中能够看到想要实现对所有观察者的告诉须要满足 指标对象状态扭转 这一必要条件。为了保障获取状态和观察者汇合时线程平安,这里应用了 synchronized 关键字和局部变量。然而同步代码块并没有蕴含调用观察者 update 办法,这就导致了可能会呈现有观察者没有收到告诉或者收到谬误的告诉。
对于 JDK 提供的观察者模式,应用的流程为:Observable.setChanged() -> Observable.notifyObservers(Object arg)。2. JDK 中的事件驱动模型
除了观察者模式,JDK 还实现了对事件驱动模型的反对。为此,JDK 提供了 EventObject 类 和 EventListener 接口来反对这一模型。前者代表了事件驱动模型中的 事件对象,后者则代表了 事件监听者。
首先咱们来看下 EventObject 的构造函数: -
public EventObject(Object source) {
2. if (source == null) 3. throw new IllegalArgumentException("null source"); 4. this.source = source;
- }
能够看到,在构造函数中必须传入一个 source 对象,该对象在官网正文中被定义为最后产生事件的对象。这个解释乍一看还是有点形象,联合下面交通灯的例子可能会更好了解一点。
在交通灯的例子中,交通灯就是 事件源 ,而交通灯变色就是 事 件,司机就是事件监听者。司机作为事件监听者理论察看的对象是交通灯,当产生交通灯变色事件之后,司机会依据交通灯变色事件进行相应的解决(也就是进行事件的解决)。
依据下面的逻辑咱们不难看到,司机这一事件监听者实际上是注册到交通灯这一事件源上,而后去解决交通灯所产生的事件。这里咱们能够看下 JDK 提供的事件监听者接口 EventListener,能够看到这里只是申明了一个接口,外面没有任何的办法。从集体角度来了解,这可能是作者思考到众口难调的状况,与其费尽周折想一个通用的办法,不如单纯定义一个接口,让使用者自由发挥。3. Spring 中的事件驱动模型 – 公布 / 订阅模式
Spring 框架对于事件驱动模型做了数据模型上的进一步明确,在原有的概念上又新增了 事件发布者 的角色,由此失去了一个新的模式——公布 / 订阅模式。
在 JDK 的根底上,Spring 框架提供了 ApplicationEvent、ApplicationListener 和 ApplicationEventPublisher 三个根底类来反对公布 / 订阅模式。其中 ApplicationEvent 和 ApplicationListener 别离继承了 EventObject 和 EventListener,其作用也和这两个类雷同,就不再过多赘述。这里具体关注一下 ApplicationEventPublisher 这个新引入的类,这个新引入的类就对应着下面事件驱动模型中事件源这一角色,区别于 JDK 中的自在奔放,这里将事件源定义为了事件发布者,并提供了一下两个办法: - @FunctionalInterface
-
public interface ApplicationEventPublisher {
- /**
-
- 告诉所有注册到发布者下面的监听器进行对应的事件处理
- *
-
- @param event 用于公布的事件,这里的事件对象必须是 ApplicationEvent 的基类
-
*/
- default void publishEvent(ApplicationEvent event) {
-
publishEvent((Object) event);
- }
- /**
-
- 告诉所有注册到发布者下面的监听器进行对应的事件处理
-
-
- @param event 用于公布的事件,任意类型事件都能够进行解决
-
*/
- void publishEvent(Object event);
- }
能够看到为了保障扩展性和自在行,Spring 即提供了基于 ApplicationEvent 类型的事件公布办法,也提供了 Object 类型的事件处理。这里咱们选取 AbstractApplicationContext 这一 ApplicationEvent 的基类来一窥 Spring 中事件公布的逻辑: -
@Override
- public void publishEvent(ApplicationEvent event) {
-
publishEvent(event, null);
- }
- protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
- Assert.notNull(event, “Event must not be null”);
- // 将事件包装成 ApplicationEvent
- ApplicationEvent applicationEvent;
-
if (event instanceof ApplicationEvent) {
- applicationEvent = (ApplicationEvent) event;
-
} else {
- applicationEvent = new PayloadApplicationEvent<>(this, event);
- if (eventType == null) {
-
eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
- }
- }
- // 如果可能,当初立刻进行多播
- // 或一旦初始化多播器就懈怠地进行多播
-
if (this.earlyApplicationEvents != null) {
- this.earlyApplicationEvents.add(applicationEvent);
-
} else {
- // 进行事件的播送,这里是进行播送的要害
- getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
- }
- // 通过父类的 context 进行事件公布
-
if (this.parent != null) {
- if (this.parent instanceof AbstractApplicationContext) {
-
((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
- }
- else {
-
this.parent.publishEvent(event);
- }
-
}
- }
- /**
-
- 将事件播送给对应的监听者
- */
- public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
- ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
- Executor executor = getTaskExecutor();
-
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
- if (executor != null) {
-
executor.execute(() -> invokeListener(listener, event));
- }
- else {
-
invokeListener(listener, event);
- }
-
}
- }
除了事件筹备的过程,进行事件播送告诉给对应的监听者,而后调用监听者对应的办法,这一过程和下面看到过的 Observable 告诉监听器的办法基本相同。然而区别于 JDK 中的同步解决,Spring 中的事件处理如果存在线程池的话,还应用了线程池就行异步解决对应的事件,进一步将发布者和监听者做理解耦。
四、总结
观察者模式最大的特定是建设了一个一对多且涣散的耦合关系,察看指标只须要维持一个形象观察者汇合,毋庸感知具体的观察者有哪些。这样一个涣散的耦合关系有利于察看指标和观察者各自进行对应的形象解决,很好的体现了开闭准则。
当然,观察者模式也有其弊病,比方只定义了一对多的关系,无奈解决多对多的场景;又比方只能感知察看指标产生了变动,然而具体如何变动却无奈理解到,等等。这些都是观察者模式无奈解决的场景或存在的问题。
本文转载:
https://developer.aliyun.com/…