Java设计模式之观察者模式

Ebony ·
更新时间:2024-11-11
· 895 次阅读

观察者模式是极其重要的一个设计模式,也是我几年开发过程中使用最多的设计模式,本文首先概述观察者模式的基本概念和Demo实现,接着是观察者模式在Java和Spring中的应用,最后是对观察者模式的应用场景和优缺点进行总结。

一、概念理解

观察者模式:定义对象之间的一种一对多的依赖关系,使得每当一个对象的状态发生变化时,其相关的依赖对象都可以得到通知并被自动更新。主要用于多个不同的对象对一个对象的某个方法会做出不同的反应!

概念啥意思呢?也就是说,如果使用观察者模式在A的业务逻辑中调用B的业务逻辑,即使B的业务逻辑报错了,仍然不影响A的执行。

比如,在我最近公司开发商城系统的过程中,提交订单成功以后要删除购物车中的信息,如果我先写订单提交逻辑,接着写删除购物车逻辑,这样当然没有什么问题,但是这样程序的健壮性太差了。

应该将该业务分成两步,一是处理订单成功处理逻辑,二是删除购物车中的信息。即使删除购物车报错了,提交订单逻辑仍然不影响。

那应该怎么做才能让他们互不影响呢?需要在购物车对象中要有一个方法用于删除购物车,还要有一个对象A用于注入(add)购物车对象和通知(notify)购物车执行它的方法。

在执行时先调用对象A的add方法将购物车对象添加到对象A中,在订单提交成功以后,调用对象A的通知notify购物车方法执行清除购物车逻辑。

在观察者模式中,购物车就称为观察者,对象A就称为目标对象。在面向接口编程原则下,观察者模式应该包括四个角色:

1、目标接口(subject) :它是一个抽象类,也是所有目标对象的父类。它用一个列表记录当前目标对象有哪些观察者对象,并提供增加、删除观察者对象和通知观察者对象的方法声明。

2、具体目标类:可以有多个不同的具体目标类,它们同时继承Subject类。一个目标对象就是某个具体目标类的对象,一个具体目标类负责定义它自身的事务逻辑,并在状态改变时通知它的所有观察者对象。

3、观察者接口(Listener) 它也是一个抽象类,是所有观察者对象的父类;它为所有的观察者对象都定义了一个名为update(notify)的方法。当目标对象的状态改变时,它就是通过调用它的所有观察者对象的update(notify)方法来通知它们的。

4、具体观察者类,可以有多个不同的具体观察者类,它们同时继承Listener类。一个观察者对象就是某个具体观察者类的对象。每个具体观察者类都要重定义Listener类中定义的update(notify)方法,在该方法中实现它自己的任务逻辑,当它被通知的时候(目标对象调用它的update(notify)方法)就执行自己特有的任务。在我们的例子中是购物车观察者,当然还能有别的,如日志观察者。

我们基于四个角色实现demo。

二、案例实现

目标接口:包括注册、移除、通知监听者的方法声明。

/** * 这是被观察的对象 * 目标类 * @author tcy * @Date 17-09-2022 */ public interface SubjectAbstract<T> { //注册监听者 public void registerListener(T t); //移除监听者 public void removeListener(T t); //通知监听者 public void notifyListener(); }

目标接口实现:里面需要一个listenerList数组存储所有的观察者,需要定义add和remove观察者的方法,需要给出notify方法通知所有的观察者对象。

/** * * 具体目标类 * @author tcy * @Date 17-09-2022 */ public class SubjectImpl implements SubjectAbstract<ListenerAbstract> { //监听者的注册列表 private List<ListenerAbstract> listenerList = new ArrayList<>(); @Override public void registerListener(ListenerAbstract myListener) { listenerList.add(myListener); } @Override public void removeListener(ListenerAbstract myListener) { listenerList.remove(myListener); } @Override public void notifyListener() { for (ListenerAbstract myListener : listenerList) { System.out.println("收到推送事件,开始调用异步逻辑..."); myListener.onEvent(); } } }

观察者接口:声明响应方法

/** * * 观察者-接口 * @author tcy * @Date 17-09-2022 */ public interface ListenerAbstract { void onEvent(); }

观察者接口:实现响应方法,处理清除购物车的逻辑。

/** * 具体观察者类 购物车 * @author tcy * @Date 17-09-2022 */ public class ListenerMyShopCart implements ListenerAbstract { @Override public void onEvent() { //...省略购物车处理逻辑 System.out.println("删除购物车中的信息..."); } }

我们使用Client模拟提交订单操作。

/** * 先使用具体目标对象的registerListener方法添加具体观察者对象, * 然后调用其notify方法通知观察者 * @author tcy * @Date 17-09-2022 */ public class Client { public static void main(String[] args) { System.out.println("订单成功处理逻辑..."); //创建目标对象 SubjectImpl subject=new SubjectImpl(); //具体观察者注册入 目标对象 ListenerMyShopCart shopCart=new ListenerMyShopCart(); //向观察者中注册listener subject.registerListener(shopCart); //发布事件,通知观察者 subject.notifyListener(); } }

这样就实现了订单的处理逻辑和购物车的逻辑解耦,即使购物车逻辑报错也不会影响订单处理逻辑。

既然观察者模式是很常用的模式,而且抽象观察者和抽象目标类方法声明都是固定的,作为高级语言Java,Java设计者干脆内置两个接口,开发者直接实现接口就能使用观察者模式。

三、Java中的观察者模式

在 Java 中,通过 java.util.Observable 类和 java.util.Observer 接口定义观察者模式,只要实现它们的子类就可以编写观察者模式实例。

Observable 类是抽象目标类,它有一个 Vector 向量,用于保存所有要通知的观察者对象,下面来介绍它最重要的 3 个方法。

void addObserver(Observer o) 方法:用于将新的观察者对象添加到向量中。

void notifyObservers(Object arg) 方法:调用向量中的所有观察者对象的 update() 方法,通知它们数据发生改变。通常越晚加入向量的观察者越先得到通知。

void setChange() 方法:用来设置一个 boolean 类型的内部标志位,注明目标对象发生了变化。当它为真时,notifyObservers() 才会通知观察者。

Observer 接口是抽象观察者,它监视目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用 void update(Observable o,Object arg) 方法,进行相应的工作。

我们基于Java的两个接口,改造我们的案例。

具体目标类:

/** * 具体目标类 * @author tcy * @Date 19-09-2022 */ public class SubjectObservable extends Observable { public void notifyListener() { super.setChanged(); System.out.println("收到推送的消息..."); super.notifyObservers(); //通知观察者购物车事件 } }

具体观察者类:

/** * 观察者实现类 * @author tcy * @Date 19-09-2022 */ public class ShopCartObserver implements Observer { @Override public void update(Observable o, Object arg) { System.out.println("清除购物车..."); } }

依旧是Client模拟订单处理逻辑。

/** * @author tcy * @Date 19-09-2022 */ public class Client { public static void main(String[] args) { System.out.println("订单提交成功..."); SubjectObservable observable = new SubjectObservable(); Observer shopCartObserver = new ShopCartObserver(); //购物车 observable.addObserver(shopCartObserver); observable.notifyListener(); } }

这样也能实现观察者逻辑,但Java中的观察者模式有一定的局限性。

Observable是个类,而不是一个接口,没有实现Serializable,所以,不能序列化和它的子类,而且他是线程不安全的,无法保证观察者的执行顺序。在JDK9之后已经启用了。

写Java的恐怕没有不用Spring的了,作为优秀的开源框架,Spring中也有观察者模式的大量应用,而且Spring是在java的基础之上改造的,很好的规避了Java观察者模式的不足之处。

四、Spring如何使用观察者模式

在第一章节典型的观察者模式中包含四个角色:目标类、目标类实现、观察者、观察者实现类。而在Spring下的观察者模式略有不同,Spring对其做了部分改造。

事件:

Spring中定义最顶层的事件ApplicationEvent,这个接口最终还是继承了EventObject接口。

只是在基础之上增加了构造和获取当前时间戳的方法,Spring所有的事件都要实现这个接口,比如Spring中内置的ContextRefreshedEvent、ContextStartedEvent、ContextStoppedEvent...看名字大概就知道这些事件用于哪些地方,分别是容器刷新后、开始时、停止时...

目标类接口:

Spirng中的ApplicationEventMulticaster接口就是实例中目标类,我们可以对比我们的目标接口和ApplicationEventMulticaster接口,长的非常像。

观察者接口:

观察者ApplicationListener用于监听事件,只有一个方法onApplicationEvent事件发生后该事件执行。与我们样例中的抽象观察者并无太大的不同。

目标类实现:

在我们案例中目标类的职责直接在一个类中实现,注册监听器、广播事件(调用监听器方法)。

在Spring中两个实现类分别拆分开来,Spring启动过程中会调用registerListeners()方法,看名字我们大概就已经知道是注册所有的监听器,该方法完成原目标类的注册监听器职责。

在Spring中事件源ApplicationContext用于广播事件,用户不必再显示的调用监听器的方法,交给Spring调用,该方法完成原目标类的广播事件职责。

我们基于Spring的观察者模式继续改造我们的案例。

购物车事件:

/** * 购物车事件 * @author tcy * @Date 19-09-2022 */ @Component public class EventShopCart extends ApplicationEvent { private String orderId; public EventShopCart(Object source, String orderId) { super(source); this.orderId=orderId; } public EventShopCart() { super(1); } }

发布者(模拟Spring调用监听器的方法,实际开发不需要写):

/** * 发布者 * @author tcy * @Date 19-09-2022 */ @Component public class MyPublisher implements ApplicationContextAware { private ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } /** * 发布事件 * 监听该事件的监听者都可以获取消息 * * @param myEvent */ public void workEvent(EventShopCart myEvent) { //该方法会调用监听器实现的方法 applicationContext.publishEvent(myEvent); } }

监听者:

/** * 监听者 * @author tcy * @Date 19-09-2022 */ @Component public class ListenerShopCart implements ApplicationListener<EventShopCart> { @Override public void onApplicationEvent(EventShopCart myEvent) { System.out.println("清除购物车成功..."); } }

Client模拟调用:

/** * @author tcy * @Date 19-09-2022 */ public class Client { public static void main(String[] args) { ApplicationContext ac =new AnnotationConfigApplicationContext("cn.sky1998.behavior.observer.spring"); System.out.println("订单提交成功..."); MyPublisher bean = ac.getBean(MyPublisher.class); EventShopCart myEvent = ac.getBean(EventShopCart.class); bean.workEvent(myEvent); } }

通过Spring实现观察者模式比我们手动写简单的多。

使用Spring实现观察者模式时,观察者接口、目标接口、目标实现,我们都不需要管,只负责继承ApplicationEvent类定义我们自己的事件,并实现ApplicationListener<自定义事件>接口实现我们的观察者,并在对应的业务中调用applicationContext.publishEvent(new ShopCartEvent(cmOrderItemList)),即实现了观察者模式。

读者可以拉取完整代码本地学习,实现代码均测试通过上传到码云,本地源码下载。

五、总结

Spring使用观察者模式我在很久之前就使用过,但是并不清楚为什么要这样写,学了观察者模式以后,写起来变得通透多了。

虽然观察者模式的概念是:一对多的依赖关系,但不一定观察者有多个才能使用,我们的例子都是使用的一个观察者。

它很好的降低了目标与观察者之间的耦合关系,目标与观察者建立一套触发机制,也让他成为了最常见的设计模式。

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对软件开发网的支持。如果你想了解更多相关内容请查看下面相关链接



java设计模式 JAVA 观察者模式

需要 登录 后方可回复, 如果你还没有账号请 注册新账号