Skip to main content

moregeek program

设计模式之观察者模式_程序员田同学的博客-多极客编程

观察者模式是极其重要的一个设计模式,也是我几年开发过程中使用最多的设计模式,本文首先概述观察者模式的基本概念和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接口。


image-20220921091811019


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


目标类接口:


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


image-20220921092108263


观察者接口:


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


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


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


设计模式的学习要成体系,推荐你看我往期发布的设计模式文章。


一、设计模式概述


二、设计模式之工厂方法和抽象工厂


三、设计模式之单例和原型


四、设计模式之建造者模式


五、设计模式之代理模式


六、设计模式之适配器模式


七、设计模式之桥接模式


八、设计模式之组合模式


九、设计模式之装饰器模式


十、设计模式之外观模式


十一、外观模式之享元模式


十二、设计模式之责任链模式


十三、设计模式之命令模式


十四、设计模式之解释器模式


十五、设计模式之迭代器模式


十六、设计模式之中介者模式


十七、设计模式之备忘录模式


©著作权归作者所有:来自51CTO博客作者程序员田同学的原创作品,请联系作者获取转载授权,否则将追究法律责任

认识java的整形数据结构_华为云开发者社区的博客-多极客编程

摘要:java中一切都是对象,为什么int不用创建对象实例化,而可以直接使用?本文分享自华为云社区《​​【Java】对基本类型-整型数据结构的认识​​》,作者: huahua.Dr 。整型数据类型有两个:基本类型和引用类型(包装类)整数型基本类型:byte,int,short,long其引用类型:Byte,Integer,Short,Long他们之前主要的区别在于:存储占用的空间不同,分别是1,2

设计模式之备忘录模式_程序员田同学的博客-多极客编程

无论是我们在使用word还是记事本,系统都会为我们提供撤销的功能,这几乎是人人都会使用到的功能,而在我们实际开发中,会不会存在一个很复杂的对象,当更改了其中的某一个属性以后,也提供撤销的功能,可以快速恢复到更新前的状态。提供该功能的模式也正是今天的主题——备忘录模式。 一、概念理解 书上备忘录的解释是,在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢

设计模式之中介者模式_程序员田同学的博客-多极客编程

在我们实际业务中,可能存在多个类之间相互调用,形成了一个复杂的网状结构。这时候就需要有一种模式去“捋顺”他们之间的关系,引出一个中间者让类之间不再相互调用,该模式就是我们今天的主人公——中介者模式。 一、概念理解 我们先看中介者模式的官方概念:用一个中介者对象来封装一系列的对象交互,中介者使各对象不需要显示地相互引用,从而使其松散耦合,而且可以独立地改变它们之间的交互。 大白话解释就是,引入一个“

第一场面试_延年有余的博客-多极客编程

简单三分钟自我介绍 自我介绍这里一笔带过,给对面介绍自己内在 + 外在 + 校园经历 + 校园项目 + 意向岗位 技术面 1. Spring 原理篇 1.1 有使用过Spring吗,说一说它的 SpringMVC 原理 主要有5个组件,前端控制器、映射器、处理器、处理器适配器、视图解析器 前端控制器也就是中央处理器,它主要负责前端用户的请求和对其他组件的转发调用; 前端控制器接收到请求后

设计模式之状态模式_程序员田同学的博客-多极客编程

实际开发中订单往往都包含着订单状态,用户每进行一次操作都要切换对应的状态,而每次切换判断当前的状态是必须的,就不可避免的引入一系列判断语句,为了让代码更加清晰直观,我们引入今天的主角——状态模式。 一、概念理解 假设订单状态有,下单、发货、确认收货,如果用户确认收货,在常规编程中就要判断当前用户的状态,然后再修改状态,如果这种情况下使用状态模式。 将各个状态都抽象成一个状态类,比如下单状态类、发货

设计模式之策略模式_程序员田同学的博客-多极客编程

在一个收银系统中,如果普通用户、中级会员、高级会员分别对应着不同的优惠策略,常规编程就要使用一系列的判断语句,判断用户类型,这种情况下就可以使用策略模式。 一、概念理解 策略模式的概念很好理解,它将对象和行为分开,将行为定义为 一个行为接口和具体行为的实现,每个if判断都可以理解为一个策略。 如果在收银系统中使用策略模式,要将普通、中级、高级会员分别定义一个具体策略类,并实现各自的方法,定义一个环

微信原生组件|基于小程序实现音视频通话_mb62c3fbf0624ad的博客-多极客编程

1 微信小程序原生推拉流组件功能简介本文将介绍如何使用微信小程序原生推拉流组件 \ 进行推拉流,快速实现一个简单的实时音视频通话。由于微信小程序原生推拉流组件使用起来比较复杂,推荐开发者使用即构封装的音视频SDK \ 组件实现视频通话,可参考 ​​实现视频通话​​。2 实现微信小程序音视频通话的前提条件在实现基本的实时音视频功能之前,请确保:已在项目中集成 ZEGO Express SDK 即构音

认识java的整形数据结构_华为云开发者社区的博客-多极客编程

摘要:java中一切都是对象,为什么int不用创建对象实例化,而可以直接使用?本文分享自华为云社区《​​【Java】对基本类型-整型数据结构的认识​​》,作者: huahua.Dr 。整型数据类型有两个:基本类型和引用类型(包装类)整数型基本类型:byte,int,short,long其引用类型:Byte,Integer,Short,Long他们之前主要的区别在于:存储占用的空间不同,分别是1,2

单元测试的内容与步骤_多测师11的博客-多极客编程

  单元测试的内容与步骤  单元测试针对程序模块,进行正确性检验的测试。其目的在于发现各模块内部可能存在的各种差错。单元测试需要从程序的内部结构出发设计测试用例。多个模块可以平行地独立进行单元测试。  ①单元测试的内容  模块接口测试:对通过被测模块的数据流进行测试。为此,对模块接口,包括参数表、调用子模块的参数、全程数据、文件输入/输出操作都必须检查。  局部数据结构测试:设计测试用例检查数据类

兼容性测试包含哪几类呢?_多测师11的博客-多极客编程

  兼容性测试包含哪几类呢?  (1)浏览器方面  关于浏览器的兼容性测试,主要是检查页面的交互、元素和样式展示是否正常。我们都知道,目前市面上主流的浏览器非常多,像:360、搜狗、火狐等等。  在进行测试的时候,由于兼容性问题很多,所以小编给大家整理了一些测试注意事项,一起来看一下:  ①明确目标用户:虽然产品经理会统计主流的浏览器和稳定的版本有哪些,但是,作为测试人员还是应该深入目标用户,去了

第一场面试_延年有余的博客-多极客编程

简单三分钟自我介绍 自我介绍这里一笔带过,给对面介绍自己内在 + 外在 + 校园经历 + 校园项目 + 意向岗位 技术面 1. Spring 原理篇 1.1 有使用过Spring吗,说一说它的 SpringMVC 原理 主要有5个组件,前端控制器、映射器、处理器、处理器适配器、视图解析器 前端控制器也就是中央处理器,它主要负责前端用户的请求和对其他组件的转发调用; 前端控制器接收到请求后

轻量级工作流引擎的设计与实现_京东云官方的博客-多极客编程

一、什么是工作流引擎工作流引擎是驱动工作流执行的一套代码。至于什么是工作流、为什么要有工作流、工作流的应用景,同学们可以看一看网上的资料,在此处不在展开。二、为什么要重复造轮子开源的工作流引擎很多,比如 activiti、flowable、Camunda 等,那么,为什么没有选它们呢?基于以下几点考虑:最重要的,满足不了业务需求,一些特殊的场景无法实现。有些需求实现起来比较绕,更有甚者,需要直接修