伙伴云客服论坛»论坛 S区 S软件开发 查看内容

0 评论

0 收藏

分享

Java设计形式之观察者形式

观察者形式是极其重要的一个设计形式,也是我几年开发过程中使用最多的设计形式,本文首先概述观察者形式的根本概念和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。
二、案例实现

目的接口:包括注册、移除、通知监听者的方法声明。
  1. /**
  2. * 这是被观察的对象
  3. * 目的类
  4. * @author tcy
  5. * @Date 17-09-2022
  6. */
  7. public interface SubjectAbstract<T> {
  8.     //注册监听者
  9.     public void registerListener(T t);
  10.     //移除监听者
  11.     public void removeListener(T t);
  12.     //通知监听者
  13.     public void notifyListener();
  14. }
复制代码
目的接口实现:里面需要一个listenerList数组存储所有的观察者,需要定义add和remove观察者的方法,需要给出notify方法通知所有的观察者对象。
  1. /**
  2. *
  3. * 详细目的类
  4. * @author tcy
  5. * @Date 17-09-2022
  6. */
  7. public class SubjectImpl implements SubjectAbstract<ListenerAbstract> {
  8.     //监听者的注册列表
  9.     private List<ListenerAbstract> listenerList = new ArrayList<>();
  10.     @Override
  11.     public void registerListener(ListenerAbstract myListener) {
  12.         listenerList.add(myListener);
  13.     }
  14.     @Override
  15.     public void removeListener(ListenerAbstract myListener) {
  16.         listenerList.remove(myListener);
  17.     }
  18.     @Override
  19.     public void notifyListener() {
  20.         for (ListenerAbstract myListener : listenerList) {
  21.             System.out.println("收到推送事件,开端调用异步逻辑...");
  22.             myListener.onEvent();
  23.         }
  24.     }
  25. }
复制代码
观察者接口:声明响应方法
  1. /**
  2. *
  3. * 观察者-接口
  4. * @author tcy
  5. * @Date 17-09-2022
  6. */
  7. public interface ListenerAbstract {
  8.     void onEvent();
  9. }
复制代码
观察者接口:实现响应方法,处置肃清购物车的逻辑。
  1. /**
  2. * 详细观察者类 购物车
  3. * @author tcy
  4. * @Date 17-09-2022
  5. */
  6. public class ListenerMyShopCart implements ListenerAbstract {
  7.     @Override
  8.     public void onEvent() {
  9.             //...省略购物车处置逻辑
  10.             System.out.println("删除购物车中的信息...");
  11.     }
  12. }
复制代码
我们使用Client模仿提交订单操作。
  1. /**
  2. * 先使用详细目的对象的registerListener方法添加详细观察者对象,
  3. * 然后调用其notify方法通知观察者
  4. * @author tcy
  5. * @Date 17-09-2022
  6. */
  7. public class Client {
  8.     public static void main(String[] args) {
  9.         System.out.println("订单胜利处置逻辑...");
  10.         //创建目的对象
  11.         SubjectImpl subject=new SubjectImpl();
  12.         //详细观察者注册入 目的对象
  13.         ListenerMyShopCart shopCart=new ListenerMyShopCart();
  14.         //向观察者中注册listener
  15.         subject.registerListener(shopCart);
  16.         //发布事件,通知观察者
  17.         subject.notifyListener();
  18.     }
  19. }
复制代码
这样就实现了订单的处置逻辑和购物车的逻辑解耦,即便购物车逻辑报错也不会影响订单处置逻辑。
既然观察者形式是很常用的形式,而且笼统观察者和笼统目的类方法声明都是固定的,作为高级语言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的两个接口,改造我们的案例。
详细目的类:
  1. /**
  2. * 详细目的类
  3. * @author tcy
  4. * @Date 19-09-2022
  5. */
  6. public class SubjectObservable extends Observable {
  7.     public void notifyListener() {
  8.         super.setChanged();
  9.         System.out.println("收到推送的消息...");
  10.         super.notifyObservers();    //通知观察者购物车事件
  11.     }
  12. }
复制代码
详细观察者类:
  1. /**
  2. * 观察者实现类
  3. * @author tcy
  4. * @Date 19-09-2022
  5. */
  6. public class ShopCartObserver implements Observer {
  7.     @Override
  8.     public void update(Observable o, Object arg) {
  9.         System.out.println("肃清购物车...");
  10.     }
  11. }
复制代码
照旧是Client模仿订单处置逻辑。
  1. /**
  2. * @author tcy
  3. * @Date 19-09-2022
  4. */
  5. public class Client {
  6.     public static void main(String[] args) {
  7.         System.out.println("订单提交胜利...");
  8.         SubjectObservable observable = new SubjectObservable();
  9.         Observer shopCartObserver = new ShopCartObserver(); //购物车
  10.         observable.addObserver(shopCartObserver);
  11.         observable.notifyListener();
  12.     }
  13. }
复制代码
这样也能实现观察者逻辑,但Java中的观察者形式有一定的局限性。
Observable是个类,而不是一个接口,没有实现Serializable,所以,不能序列化和它的子类,而且他是线程不安全的,无法保证观察者的执行顺序。在JDK9之后已经启用了。
写Java的恐怕没有不用Spring的了,作为优秀的开源框架,Spring中也有观察者形式的大量应用,而且Spring是在java的根底之上改造的,很好的躲避了Java观察者形式的缺乏之处。
四、Spring如何使用观察者形式

在第一章节典型的观察者形式中包含四个角色:目的类、目的类实现、观察者、观察者实现类。而在Spring下的观察者形式略有不同,Spring对其做了部分改造。
事件:
Spring中定义最顶层的事件ApplicationEvent,这个接口最终还是继承了EventObject接口。
Java设计形式之观察者形式-1.png

只是在根底之上增加了构造和获取当前时间戳的方法,Spring所有的事件都要实现这个接口,比如Spring中内置的ContextRefreshedEvent、ContextStartedEvent、ContextStoppedEvent...看名字大约就晓得这些事件用于哪些地方,分别是容器刷新后、开端时、停止时...
目的类接口:
Spirng中的ApplicationEventMulticaster接口就是实例中目的类,我们可以对比我们的目的接口和ApplicationEventMulticaster接口,长的非常像。
Java设计形式之观察者形式-2.png

观察者接口:
观察者ApplicationListener用于监听事件,只要一个方法onApplicationEvent事件发生后该事件执行。与我们样例中的笼统观察者并无太大的不同。
目的类实现:
在我们案例中目的类的职责直接在一个类中实现,注册监听器、广播事件(调用监听器方法)。
在Spring中两个实现类分别拆分开来,Spring启动过程中会调用registerListeners()方法,看名字我们大约就已经晓得是注册所有的监听器,该方法完成原目的类的注册监听器职责。
在Spring中事件源ApplicationContext用于广播事件,用户不用再显示的调用监听器的方法,交给Spring调用,该方法完成原目的类的广播事件职责。
我们基于Spring的观察者形式继续改造我们的案例。
购物车事件:
  1. /**
  2. * 购物车事件
  3. * @author tcy
  4. * @Date 19-09-2022
  5. */
  6. @Component
  7. public class EventShopCart extends ApplicationEvent {
  8.     private String orderId;
  9.     public EventShopCart(Object source, String orderId) {
  10.         super(source);
  11.         this.orderId=orderId;
  12.     }
  13.     public EventShopCart() {
  14.         super(1);
  15.     }
  16. }
复制代码
发布者(模仿Spring调用监听器的方法,实际开发不需要写):
  1. /**
  2. * 发布者
  3. * @author tcy
  4. * @Date 19-09-2022
  5. */
  6. @Component
  7. public class MyPublisher implements ApplicationContextAware {
  8.     private ApplicationContext applicationContext;
  9.     @Override
  10.     public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
  11.         this.applicationContext = applicationContext;
  12.     }
  13.     /**
  14.      * 发布事件
  15.      * 监听该事件的监听者都可以获取消息
  16.      *
  17.      * @param myEvent
  18.      */
  19.     public void workEvent(EventShopCart myEvent) {
  20.         //该方法会调用监听器实现的方法
  21.         applicationContext.publishEvent(myEvent);
  22.     }
  23. }
复制代码
监听者:
  1. /**
  2. * 监听者
  3. * @author tcy
  4. * @Date 19-09-2022
  5. */
  6. @Component
  7. public class ListenerShopCart implements ApplicationListener<EventShopCart> {
  8.     @Override
  9.     public void onApplicationEvent(EventShopCart myEvent) {
  10.         System.out.println("肃清购物车胜利...");
  11.     }
  12. }
复制代码
Client模仿调用:
  1. /**
  2. * @author tcy
  3. * @Date 19-09-2022
  4. */
  5. public class Client {
  6.     public static void main(String[] args) {
  7.         ApplicationContext ac =new AnnotationConfigApplicationContext("cn.sky1998.behavior.observer.spring");
  8.         System.out.println("订单提交胜利...");
  9.         MyPublisher bean = ac.getBean(MyPublisher.class);
  10.         EventShopCart myEvent = ac.getBean(EventShopCart.class);
  11.         bean.workEvent(myEvent);
  12.     }
  13. }
复制代码
通过Spring实现观察者形式比我们手动写简单的多。
使用Spring实现观察者形式时,观察者接口、目的接口、目的实现,我们都不需要管,只负责继承ApplicationEvent类定义我们自己的事件,并实现ApplicationListener<自定义事件>接口实现我们的观察者,并在对应的业务中调用applicationContext.publishEvent(new ShopCartEvent(cmOrderItemList)),即实现了观察者形式。
读者可以拉取完好代码本地学习,实现代码均测试通过上传到码云,本地源码下载。
五、总结

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

回复

举报 使用道具

全部回复
暂无回帖,快来参与回复吧
本版积分规则 高级模式
B Color Image Link Quote Code Smilies

微黄
注册会员
主题 13
回复 20
粉丝 0
|网站地图
快速回复 返回顶部 返回列表