当前位置: 首页 > news >正文

设计模式探索:观察者模式

1. 观察者模式

1.1 什么是观察者模式

观察者模式用于建立一种对象与对象之间的依赖关系,当一个对象发生改变时将自动通知其他对象,其他对象会相应地作出反应。
在这里插入图片描述

在观察者模式中有如下角色:

  • Subject(抽象主题/被观察者): 抽象主题角色把所有观察者对象保存在一个集合里,每个主题可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
  • ConcreteSubject(具体主题/具体被观察者): 该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
  • Observer(抽象观察者): 观察者的抽象类,定义了一个更新接口,使得在得到主题更改通知时更新自己。
  • ConcreteObserver(具体观察者): 实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。在具体观察者中维护一个指向具体目标对象的引用,存储具体观察者的有关状态,这些状态需要与具体目标保持一致。
1.2 观察者模式实现
  • 观察者
/*** 抽象观察者*/
public interface Observer {// update方法: 为不同观察者的更新(响应)行为定义相同的接口,不同的观察者对该方法有不同的实现void update();
}/*** 具体观察者*/
public class ConcreteObserverOne implements Observer {@Overridepublic void update() {// 获取消息通知,执行业务代码System.out.println("ConcreteObserverOne 得到通知!");}
}/*** 具体观察者*/
public class ConcreteObserverTwo implements Observer {@Overridepublic void update() {// 获取消息通知,执行业务代码System.out.println("ConcreteObserverTwo 得到通知!");}
}
  • 被观察者
/*** 抽象目标类*/
public interface Subject {void attach(Observer observer);void detach(Observer observer);void notifyObservers();
}/*** 具体目标类*/
public class ConcreteSubject implements Subject {// 定义集合,存储所有观察者对象private ArrayList<Observer> observers = new ArrayList<>();// 注册方法,向观察者集合中增加一个观察者@Overridepublic void attach(Observer observer) {observers.add(observer);}// 注销方法,用于从观察者集合中删除一个观察者@Overridepublic void detach(Observer observer) {observers.remove(observer);}// 通知方法@Overridepublic void notifyObservers() {// 遍历观察者集合,调用每一个观察者的响应方法for (Observer obs : observers) {obs.update();}}
}
  • 测试类
public class Client {public static void main(String[] args) {// 创建目标类(被观察者)ConcreteSubject subject = new ConcreteSubject();// 注册观察者类,可以注册多个subject.attach(new ConcreteObserverOne());subject.attach(new ConcreteObserverTwo());// 具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。subject.notifyObservers();}
}

2. 发布订阅模式与观察者模式的区别

2.1 定义上的不同

发布订阅模式属于广义上的观察者模式。

  • 发布订阅模式是最常用的一种观察者模式的实现,从解耦和重用角度来看,更优于典型的观察者模式。
2.2 两者的区别

我们来看一下观察者模式与发布订阅模式结构上的区别
在这里插入图片描述

操作流程上的区别

  • 观察者模式:数据源直接通知订阅者发生改变。
  • 发布订阅模式:数据源告诉第三方(事件通道)发生了改变,第三方再通知订阅者发生了改变。

3. 观察者模式在实际开发中的应用

3.1 实际开发中的需求场景

在我们日常业务开发中,观察者模式的一个重要作用在于实现业务的解耦。以用户注册的场景为例,假设在用户注册完成时,需要给该用户发送邮件、发送优惠券等操作,如下图所示:
在这里插入图片描述

使用观察者模式之后

在这里插入图片描述

  • UserService 在完成自身的用户注册逻辑之后,仅需要发布一个 UserRegisterEvent 事件,而无需关注其它拓展逻辑。
  • 其它 Service 可以自己订阅 UserRegisterEvent 事件,实现自定义的拓展逻辑。

3.2 Spring事件机制

Spring 基于观察者模式,实现了自身的事件机制,由三部分组成:

在这里插入图片描述

  • 事件 ApplicationEvent:通过继承它,实现自定义事件。另外,通过它的 source 属性可以获取事件timestamp 属性可以获得发生时间。
  • 事件发布者 ApplicationEventPublisher:通过它,可以进行事件的发布。
  • 事件监听器 ApplicationListener:通过实现它,进行指定类型的事件的监听。
3.3 代码实现

(1) UserRegisterEvent

  • 创建 UserRegisterEvent 事件类,继承 ApplicationEvent 类,用户注册事件。代码如下:
/*** 用户注册事件*/
public class UserRegisterEvent extends ApplicationEvent {private String username;public UserRegisterEvent(Object source) {super(source);}public UserRegisterEvent(Object source, String username) {super(source);this.username = username;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}
}

(2) UserService (事件源+事件发布)

  • 创建 UserService 类,代码如下:
/*** 事件源角色+事件发布*/
@Service
public class UserService implements ApplicationEventPublisherAware {private Logger logger = LoggerFactory.getLogger(getClass());private ApplicationEventPublisher applicationEventPublisher;@Overridepublic void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {this.applicationEventPublisher = applicationEventPublisher;}public void register(String username){// 执行注册逻辑logger.info("[register][执行用户{}的注册逻辑]", username);// 发布用户注册事件applicationEventPublisher.publishEvent(new UserRegisterEvent(this, username));}
}
  • 实现 ApplicationEventPublisherAware 接口,从而将 ApplicationEventPublisher 注入到其中。
  • 在执行完注册逻辑后,调用 ApplicationEventPublisherpublishEvent(ApplicationEvent event) 方法,发布 UserRegisterEvent 事件。

(3) 创建 EmailService

/*** 事件监听角色*/
@Service
public class EmailService implements ApplicationListener<UserRegisterEvent> {private Logger logger = LoggerFactory.getLogger(getClass());@Overridepublic void onApplicationEvent(UserRegisterEvent event) {logger.info("[onApplicationEvent][给用户({}) 发送邮件]", event.getUsername());}
}
  • 实现 ApplicationListener 接口,通过 E 泛型设置感兴趣的事件。
  • 实现 onApplicationEvent(E event) 方法,针对监听的 UserRegisterEvent 事件,进行自定义处理。

(4) CouponService

@Service
public class CouponService {private Logger logger = LoggerFactory.getLogger(getClass());@EventListener public void addCoupon(UserRegisterEvent event) {logger.info("[addCoupon][给用户({}) 发放优惠劵]", event.getUsername());}
}
  • 添加 @EventListener 注解,并设置监听的事件为 UserRegisterEvent

(5) DemoController

  • 提供 /demo/register 注册接口
@RestController
@RequestMapping("/demo")
public class DemoController {@Autowiredprivate UserService userService;@GetMapping("/register")public String register(String username) {userService.register(username);return "success";}
}
3.4 代码测试
  1. 执行 DemoApplication 类,启动项目。
  2. 调用 http://127.0.0.1:8080/demo/register?username=mashibing 接口,进行注册。IDEA 控制台打印日志如下:
// UserService 发布 UserRegisterEvent 事件
2023-04-19 16:49:40.628  INFO 9800 --- [nio-8080-exec-1] c.m.d.o.demo02.service.UserService       : [register][执行用户mashibing的注册逻辑]// EmailService 监听处理该事件
2023-04-19 16:49:40.629  INFO 9800 --- [nio-8080-exec-1] c.m.d.o.demo02.listener.EmailService     : [onApplicationEvent][给用户(mashibing) 发送邮件]// CouponService 监听处理该事件
2023-04-19 16:49:40.629  INFO 9800 --- [nio-8080-exec-1] c.m.d.o.demo02.listener.CouponService    : [addCoupon][给用户(mashibing) 发放优惠劵]

4. 观察者模式总结

1) 观察者模式的优点

  • 降低目标类和观察者之间的耦合
  • 可以实现广播机制

2) 观察者模式的缺点

  • 通知的发送会消耗一定的时间
  • 如果被观察者有循环依赖,会导致系统的崩溃

3) 观察者模式常见的使用场景

  • 一个对象的改变,需要改变其他对象的时候
  • 一个对象的改变,需要进行通知的时候

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • vue draggable组件,拖拽元素时,获取元素上在data或setup中定义的数据
  • 【matlab】随机森林客户流失预测
  • Java之网络面试经典题(一)
  • hcip暑假第二次作业
  • Elasticsearch 更新指定字段
  • C++类和对象(一)
  • C++ 定时器触发
  • 苹果电脑可以玩魔兽世界吗 魔兽世界有mac版本么 macbook 可以玩魔兽世界吗
  • Codeforces Round 957 (Div. 3)(A~E题解)
  • Flutter【组件】标签
  • 【数据结构】初探数据结构面纱:栈和队列全面剖析
  • uniapp中使用uni-ui组件库
  • NLP任务中三个概念标记、样本映射和偏移映射三个概念的浅析
  • 【MIT 6.5840/6.824】Lab1 MapReduce
  • 【记录】CSS|Tailwind 的主题定义的颜色的使用方法(--color啥的)
  • 【每日笔记】【Go学习笔记】2019-01-10 codis proxy处理流程
  • CSS中外联样式表代表的含义
  • Linux Process Manage
  • MYSQL 的 IF 函数
  • Node.js 新计划:使用 V8 snapshot 将启动速度提升 8 倍
  • Python学习之路16-使用API
  • rabbitmq延迟消息示例
  • Zsh 开发指南(第十四篇 文件读写)
  • 阿里云Kubernetes容器服务上体验Knative
  • 从0到1:PostCSS 插件开发最佳实践
  • 从零搭建Koa2 Server
  • 前端存储 - localStorage
  • 如何实现 font-size 的响应式
  • 入手阿里云新服务器的部署NODE
  • 微服务框架lagom
  • 深度学习之轻量级神经网络在TWS蓝牙音频处理器上的部署
  • 摩拜创始人胡玮炜也彻底离开了,共享单车行业还有未来吗? ...
  • 我们雇佣了一只大猴子...
  • 曾刷新两项世界纪录,腾讯优图人脸检测算法 DSFD 正式开源 ...
  • ​如何在iOS手机上查看应用日志
  • # windows 安装 mysql 显示 no packages found 解决方法
  • #《AI中文版》V3 第 1 章 概述
  • #我与Java虚拟机的故事#连载17:我的Java技术水平有了一个本质的提升
  • $(document).ready(function(){}), $().ready(function(){})和$(function(){})三者区别
  • (02)Unity使用在线AI大模型(调用Python)
  • (1)安装hadoop之虚拟机准备(配置IP与主机名)
  • (PADS学习)第二章:原理图绘制 第一部分
  • (分享)自己整理的一些简单awk实用语句
  • (介绍与使用)物联网NodeMCUESP8266(ESP-12F)连接新版onenet mqtt协议实现上传数据(温湿度)和下发指令(控制LED灯)
  • (论文阅读22/100)Learning a Deep Compact Image Representation for Visual Tracking
  • (亲测成功)在centos7.5上安装kvm,通过VNC远程连接并创建多台ubuntu虚拟机(ubuntu server版本)...
  • (转)visual stdio 书签功能介绍
  • (自用)gtest单元测试
  • .NET HttpWebRequest、WebClient、HttpClient
  • .net经典笔试题
  • .NET未来路在何方?
  • .sh 的运行
  • /var/spool/postfix/maildrop 下有大量文件
  • ?.的用法
  • ??在JSP中,java和JavaScript如何交互?