观察者形式是极其重要的一个设计形式,也是我几年开发过程中使用最多的设计形式,本文首先概述观察者形式的根本概念和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使用观察者形式我在很久之前就使用过,但是并不清楚为什么要这样写,学了观察者形式以后,写起来变得通透多了。
虽然观察者形式的概念是:一对多的依赖关系,但不一定观察者有多个才干使用,我们的例子都是使用的一个观察者。
它很好的降低了目的与观察者之间的耦合关系,目的与观察者建立一套触发机制,也让他成为了最常见的设计形式。
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对网站的支持。假设你想理解更多相关内容请查看下面相关链接 |