java观察者模式类图_设计模式之观察者模式,事件机制的底层原理

本文探讨了观察者模式,介绍了其概念和类图,通过一个简单的Java实现展示了观察者模式的用法。文章还提及了JDK中的Observable和Observer接口,并提供了具体的代码示例。此外,文章讨论了Spring事件机制,指出Spring事件基于观察者模式,并给出了Spring事件的组件角色。最后,总结了观察者模式的优点与应用场景。
摘要由CSDN通过智能技术生成

前言

本来打算这篇继续和大家一起讨论springboot启动源码的,可觉得再讲源码估计大家都没有看下去的勇气了,那么今天,我们不讲springboot的启动源码,我们先来看看一个有趣的内容,具体是什么,大家应该已经知道了,没错就是标题中的 – spring-boot事件。

可能有小伙伴觉得奇怪了,好好的源码系列不讲了,怎么突然讲一个无关紧要的内容呢?那么真的是无关紧要的内容吗?关于这个疑问后面会有解答。目前大家就权当放松了,以一种轻松的心态和我一起往下看。

观察者模式

说好的不是讲springboot的事件机制吗,怎么又讲什么观察者模式呢?心里会说:“楼主,你还好吗,你今天是不是被门夹了?”。楼主:“被门夹?不存在的,一般只有我夹门......”

还是那就话,大家放松心态慢慢看,心中的疑问先放在心里或者用笔记录下来,后面会慢慢解开的。

概念其实很简单,两个主体,一个观察者,一个被观察者,当被观察者发生变化时,观察者会有相应的动作。举几个例子,和我们日常生活息息相关的红绿灯,灯就相当于被观察者,行人就相当于观察者,当灯发生变化时,行人会有相应的动作:红灯停,绿灯行,黄灯亮了等一等。再比如我们现在玩的公众号,当我们订阅了某个公众号之后,公众号每发表一篇文章,就会向订阅了它的用户发送这篇文章,我们就可以浏览这篇文章了;当我们取消订阅了,它就不会再向我们推送这篇文章了;只要这个公众号一直在运行,就会一直有人订阅它或者取消订阅。这两个主体有个统一的称呼:被观察者成为主题(Subject),观察者仍是称为观察者(Observer)。

观察者模式还有很多其他的称谓,如发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。

理论上的东西讲的再多也只是停留在理论,下面我们来实现下,到底观察者模式是个什么神奇的东西。

类图

38ab3f9d3368521cf92e3e135565a285.png

所涉及到的角色如下:

抽象主题(Subject):提供接口,可以增加和剔除观察者对象。一般用抽象类或者接口实现。

抽象观察者(Observer):提供接口,在得到主题的通知时更新自己。一般用抽象类或者接口实现。

具体主题(ConcreteSubject):将有关状态存入具体观察者,在具体主题的内部状态发生变化时,给所有注册过的观察者发出通知。一般是具体子类实现。

具体观察者(ConcreteObserver):存储与主题的状态自恰的状态。具体观察者角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态 像协调。如果需要,具体观察者角色可以保持一个指向具体主题对象的引用

在上述类图中,ConcreteSubject中有一个存储Observer的列表,这意味着ConcreteSubject并不需要知道引用了哪些ConcreteObserver,只要实现(继承)了Observer的对象都可以存到该列表中。在需要的时候调用Observer的update方法。

一般实现

Subject:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.lee.myobserver;/*** 抽象主题

* 提供具体主题需要实现的接口*/

public interfaceSubject {/*** 注册观察者

*@paramobserver*/

voidattach(Observer observer);/*** 移除观察者

*@paramobserver*/

voiddetach(Observer observer);/*** 通知所有注册的观察者*/

voidnotifyObservers();

}

View Code

Observer:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.lee.myobserver;/*** 抽象观察者

* 提供具体观察者需要实现的接口*/

public interfaceObserver {/****/

voidupdate(String state);

}

View Code

ConcreteSubject:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.lee.myobserver.impl;importcom.lee.myobserver.Observer;importcom.lee.myobserver.Subject;importjava.util.ArrayList;importjava.util.List;/*** 具体主题*/

public class ConcreteSubject implementsSubject {private List observerList = new ArrayList<>();privateString state;

@Overridepublic voidattach(Observer observer) {this.observerList.add(observer);

System.out.println("向ConcreteSubject注册了一个观察者");

}

@Overridepublic voiddetach(Observer observer) {this.observerList.remove(observer);

System.out.println("从ConcreteSubject移除了一个观察者");

}

@Overridepublic voidnotifyObservers() {this.observerList.forEach(observer -> observer.update(this.state));

}public voidchangeState(String state) {this.state =state;this.notifyObservers();

}

}

View Code

ConcreteObserver:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.lee.myobserver.impl;importcom.lee.myobserver.Observer;/*** 具体观察者*/

public class ConcreteObserver implementsObserver {

@Overridepublic voidupdate(String state) {

System.out.println("我被通知了,我要改变状态为:" +state);

}

}

View Code

MyObserverTest:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.lee.test;importcom.lee.myobserver.Observer;importcom.lee.myobserver.impl.ConcreteObserver;importcom.lee.myobserver.impl.ConcreteSubject;public classMyObserverTest {public static voidmain(String[] args) {

ConcreteSubject subject= newConcreteSubject();

Observer observer= newConcreteObserver();

subject.attach(observer);

subject.changeState("new state");

}

}

View Code

完整代码可以从

运行结果如下:

9bbbbd9705b9598f69392712037f24e1.png

以上实现中,我们发现ConcreteSubject必须维护一个Observer列表,这会让人产生疑问:难道每个ConcreteSubject中的List会有不同吗?很明显,不会,因为List保存的类型是接口类型,那么我们是不是可以把这个维护列表放到Subject中去了?还有我们发现attach、detach、notifyObservers在各个ConcreteSubject的实现都是一样的,那么我们是不是可以共用起来呢?答案是肯定的!那么我们怎么处理了,只需要将Subject改成抽象类即可,类图如下,具体实现就交给大家自己了,有了这个类图,相信大家都可以轻松的完成代码的实现。

979a0820ac2417af6333a54843311efb.png

jdk实现

在Java语言的java.util包下,提供了一个Observable类以及一个Observer接口,构成Java语言对观察者模式的支持。

Observable:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagejava.util;/*** 抽象主题,用普通类实现*/

public classObservable {private boolean changed = false;private Vectorobs;/*** 构建一个含有0个观察者的主题*/

publicObservable() {

obs= new Vector<>();

}/*** 注册某个观察者到obs中*/

public synchronized voidaddObserver(Observer o) {if (o == null)throw newNullPointerException();if (!obs.contains(o)) {

obs.addElement(o);

}

}/*** 从obs中移除某个观察者*/

public synchronized voiddeleteObserver(Observer o) {

obs.removeElement(o);

}/*** 相当于notifyObservers(null),具体看下面那个*/

public voidnotifyObservers() {

notifyObservers(null);

}/*** 如果本对象有变化,则通知所有注册了的观察者,调用他们的update方法*/

public voidnotifyObservers(Object arg) {/** a temporary array buffer, used as a snapshot of the state of

* current Observers.*/Object[] arrLocal;synchronized (this) {if (!changed)return;

arrLocal=obs.toArray();

clearChanged();

}for (int i = arrLocal.length-1; i>=0; i--)

((Observer)arrLocal[i]).update(this, arg);

}/*** 清空obs*/

public synchronized voiddeleteObservers() {

obs.removeAllElements();

}/*** 将changed设置成true,标明本对象发生了变化*/

protected synchronized voidsetChanged() {

changed= true;

}/*** 将changed重置成false*/

protected synchronized voidclearChanged() {

changed= false;

}/*** 检测本对象是否发生了变化*/

public synchronized booleanhasChanged() {returnchanged;

}/*** 返回注册的观察者数量*/

public synchronized intcountObservers() {returnobs.size();

}

}

View Code

Observer:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagejava.util;/*** 抽象观察者,接口实现*/

public interfaceObserver {/*** 当被观察者对象发生改变时,此方法被调用*/

voidupdate(Observable o, Object arg);

}

View Code

Watched:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.lee.jdkobserver;importjava.util.Observable;/*** 具体主题*/

public class Watched extendsObservable {private String data = "";public voidchangeData(String data) {if (this.data.equals(data)) {return;

}this.data =data;

setChanged();

notifyObservers(this.data);

}

}

View Code

Watcher:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.lee.jdkobserver;importjava.util.Observable;importjava.util.Observer;/*** 具体观察者,实现jdk中的Observer*/

public class Watcher implementsObserver {

@Overridepublic voidupdate(Observable o, Object arg) {

System.out.println("数据改变成了:" +arg);

}

}

View Code

JdkObserverTest:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.lee.test;importcom.lee.jdkobserver.Watched;importcom.lee.jdkobserver.Watcher;importjava.util.Observer;public classJdkObserverTest {public static voidmain(String[] args) {

Watched watched= newWatched();

Observer observer= newWatcher();

watched.addObserver(observer);

watched.changeData("first");

watched.changeData("second");

watched.changeData("third");

watched.changeData("fourth");

}

}

View Code

完整代码可以从

运行结果如下

2a202fdcd5f0110ecdbc385733a83709.png

类图如下

05b4b80a14aacaadf79811489c82bd9a.png

jdk事件

JDK 1.0及更早版本的事件模型基于职责链模式,但是这种模型不适用于复杂的系统,因此在JDK 1.1及以后的各个版本中,事件处理模型采用基于观察者模式的委派事件模型(DelegationEvent Model, DEM),即一个Java组件所引发的事件并不由引发事件的对象自己来负责处理,而是委派给独立的事件处理对象负责。这并不是说事件模型是基于Observer和Observable的,事件模型与Observer和Observable没有任何关系,Observer和Observable只是观察者模式的一种实现而已。

java中的事件机制的参与者有3种角色

Event Eource:事件源,发起事件的主体。

Event Object:事件状态对象,传递的信息载体,就好比Watcher的update方法的参数,可以是事件源本身,一般作为参数存在于listerner 的方法之中。

Event Listener:事件监听器,当它监听到event object产生的时候,它就调用相应的方法,进行处理。

其实还有个东西比较重要:事件环境,在这个环境中,可以添加事件监听器,可以产生事件,可以触发事件监听器。

限于篇幅,具体案例实现就不讲了,大家可以去

spring事件机制

springboot没有自己的事件机制,用的就是spring的事件机制,这里希望大家别以为和标题不一致。springboot和spring的关系大家可以去捋一捋,这里可以明确的告诉大家,不是对立关系!

spring的事件机制也是从java的事件机制拓展而来,具体往下看

ApplicationEvent:Spring中所有的事件父接口,继承自java的EventObject

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

/** Copyright 2002-2015 the original author or authors.

*

* Licensed under the Apache License, Version 2.0 (the "License");

* you may not use this file except in compliance with the License.

* You may obtain a copy of the License at

*

*http://www.apache.org/licenses/LICENSE-2.0*

* Unless required by applicable law or agreed to in writing, software

* distributed under the License is distributed on an "AS IS" BASIS,

* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

* See the License for the specific language governing permissions and

* limitations under the License.*/

packageorg.springframework.context;importjava.util.EventObject;/*** Class to be extended by all application events. Abstract as it

* doesn't make sense for generic events to be published directly.

*

*@authorRod Johnson

*@authorJuergen Hoeller*/

public abstract class ApplicationEvent extendsEventObject {/**use serialVersionUID from Spring 1.2 for interoperability*/

private static final long serialVersionUID = 7099057708183571937L;/**System time when the event happened*/

private final longtimestamp;/*** Create a new ApplicationEvent.

*@paramsource the object on which the event initially occurred (never {@codenull})*/

publicApplicationEvent(Object source) {super(source);this.timestamp =System.currentTimeMillis();

}/*** Return the system time in milliseconds when the event happened.*/

public final longgetTimestamp() {return this.timestamp;

}

}

View Code

ApplicationListener:spring中所有的事件监听器父接口,继承自java的EventListener

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

/** Copyright 2002-2016 the original author or authors.

*

* Licensed under the Apache License, Version 2.0 (the "License");

* you may not use this file except in compliance with the License.

* You may obtain a copy of the License at

*

*http://www.apache.org/licenses/LICENSE-2.0*

* Unless required by applicable law or agreed to in writing, software

* distributed under the License is distributed on an "AS IS" BASIS,

* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

* See the License for the specific language governing permissions and

* limitations under the License.*/

packageorg.springframework.context;importjava.util.EventListener;/*** Interface to be implemented by application event listeners.

* Based on the standard {@codejava.util.EventListener} interface

* for the Observer design pattern.

*

*

As of Spring 3.0, an ApplicationListener can generically declare the event type

* that it is interested in. When registered with a Spring ApplicationContext, events

* will be filtered accordingly, with the listener getting invoked for matching event

* objects only.

*

*@authorRod Johnson

*@authorJuergen Hoeller

*@param the specific ApplicationEvent subclass to listen to

*@seeorg.springframework.context.event.ApplicationEventMulticaster*/@FunctionalInterfacepublic interface ApplicationListener extendsEventListener {/*** Handle an application event.

*@paramevent the event to respond to*/

voidonApplicationEvent(E event);

}

View Code

具体案例

MessageEvent:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.lee.springevent.event;importorg.springframework.context.ApplicationEvent;/*** 短信事件,事件信息的载体

* 可以从中获取事件源信息,本例中source不代表事件源*/

public class MessageEvent extendsApplicationEvent {publicMessageEvent(Object source) {super(source);

}

}

View Code

MessageListener:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.lee.springevent.listener;importcom.lee.springevent.event.MessageEvent;importorg.springframework.context.ApplicationEvent;importorg.springframework.context.ApplicationListener;public class MessageListener implementsApplicationListener{

@Overridepublic voidonApplicationEvent(ApplicationEvent event) {if(event instanceofMessageEvent) {

String phoneNumber=(String)event.getSource();

System.out.println("我已收到通知:即将向"+phoneNumber+"发短信了...");

}

}

}

View Code

MessageService:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.lee.springevent.service;importcom.lee.springevent.event.MessageEvent;importorg.springframework.beans.BeansException;importorg.springframework.context.ApplicationContext;importorg.springframework.context.ApplicationContextAware;/*** ApplicationContextAware,能够获取spring的上下文环境

*

* MessageService相当于之前说的事件环境*/

public class MessageService implementsApplicationContextAware {privateApplicationContext ctx;

@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throwsBeansException {this.ctx =applicationContext;

}public voidsendMessage(String phoneNumber)

{

MessageEvent evt= newMessageEvent(phoneNumber);//发布事件

ctx.publishEvent(evt);

}

}

View Code

spring-event.xml:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

View Code

SpringEventTest:

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.lee.test;importcom.lee.springevent.service.MessageService;importorg.springframework.context.ApplicationContext;importorg.springframework.context.support.ClassPathXmlApplicationContext;public classSpringEventTest {public static voidmain(String[] args) {

ApplicationContext applicationContext= new ClassPathXmlApplicationContext("spring-event.xml");

MessageService messageService= (MessageService) applicationContext.getBean("messageService");

messageService.sendMessage("1574480311");

}

}

View Code

执行SpringEventTest中的main方法就可以看到结果了。

更多详情请从spring-boot-test获取,在com.lee.springevent下,大家可以根据注释好好消化下。实在是不懂得话,可以放一下,下篇博文我会和大家一起着重研究下spring的事件源码,后续的博文中也都会有提到。

总结

为什么讲与springboot启动源码无关的内容

关于前言中的疑问:为什么讲一篇与spring启动源码无关的内容,有两个考虑,第一,确实是让大家放松下心态,读源码确实挺累的;第二,主要目的,就是为spring-boot-2.0.3启动源码篇二 - SpringApplication的run方法(一)之SpringApplicationRunListener做准备,里面会涉及到spring的事件机制

观察者模式优点与缺点

上面长篇大论,似乎也没讨论其优点与缺点;其实通过实现大家应该才能体会到其优点与缺点,我总结下,有什么不对的大家可以在评论区补充

优点:

(1)    主题与观察者建立一个抽象的耦合而不是紧密的耦合,降低了耦合度;主题只需要维护一个抽象观察者的集合,无需了解具体观察者,使得可以有各种各样不同的观察者实现。

(2)    支持广播通信,主题会向所有已注册的观察者对象发送通知,简化了一对多系统设计的难度。

(3)    符合“开闭原则”,增加新的具体观察者无须修改原有代码,可拓展性高。

缺点:

(1)    如果主题有很多直接或者间接观察者,那么全部通知到会很耗时。

(2)    主题与观察者之间如果存在循环依赖,可能导致系统崩溃。

观察者模式应用场景

抽象的来讲:对一个对象状态的更新,需要其他对象同步更新,而且其他对象的数量动态可变;对象仅需要将自己的更新通知给其他对象而不需要知道其他对象的细节。大家可以根据以上两点作为基本准则,某个场景是否满足观察者模式。

具体应用场景就有很多了,比如文中的事件机制、公众号订阅,tomcat源码中也有很多地方用到了(有兴趣的兄弟可以去找找)。

事件机制

jdk事件实现是基于观察者模式,而spring事件又是在jdk事件的基础上进行了拓展。

主要有四个角色

事件源:触发事件的主体,比如jdk事件案例中的UserService、spring事件案例中的MessageService、SpringBoot中的SpringApplication。

事件:事件本身,指的是EventObject中的source,具体可以是任何数据(包括事件源),用来传递数据,比如jdk事件案例中MessageEvent、spring事件案例中的MessageEvent。

事件监听器:当事件发生时,负责对事件的处理,比如jdk事件案例中MessageListener、spring事件案例中的MessageListener。

事件环境:整个事件所处的上下文,对整个事件提供支持,类似web上下文;比如jdk事件案例中的UserService、spring事件案例中的ApplicationContext

参考

《Head First 设计模式》

《java与模式》

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值