当前位置: 首页 > 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) 观察者模式常见的使用场景

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

相关文章:

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

1. 观察者模式 1.1 什么是观察者模式 观察者模式用于建立一种对象与对象之间的依赖关系&#xff0c;当一个对象发生改变时将自动通知其他对象&#xff0c;其他对象会相应地作出反应。 在观察者模式中有如下角色&#xff1a; Subject&#xff08;抽象主题/被观察者&#xf…...

Perl语言入门到高级学习

Perl语言介绍 Perl,全称为Practical Extraction and Report Language,即“实用报表提取语言”,是一种高级、通用、直译式、动态的编程语言。Perl最初由Larry Wall设计,并于1987年12月18日首次发布。经过多年的不断发展和更新,Perl已经成为一种功能丰富且应用广泛的计算机程…...

DOM 基本操作 - 获取元素

theme: smartblue 一、简介 1.1 概念 文档对象模型(Document Object Model),是 W3C 组织推荐的处理可拓展标记语言的标准编程接口。 1.2 DOM 树 二、 获取元素 获取页面中的元素主要可以使用以几种方式&#xff1a; - 根据 ID 获取 - 根据 标签名 获取 - 通过 HTML5 新增的方法…...

Google 搜索引擎:便捷高效、精准查询,带来无与伦比的搜索体验

Google搜索引擎不仅具备检索功能&#xff0c;实则是引领探索万千世界的神秘钥匙。试想&#xff0c;无论何时何地&#xff0c;只需轻触屏幕&#xff0c;所需信息即可唾手可得。便捷与高效&#xff0c;令人叹为观止。其界面设计简约直观&#xff0c;操控体验犹如与未来对话&#…...

tomcat的介绍与优化

tomcat介绍 tomcat和php一样&#xff0c;都是用来处理动态页面的。 tomcat也可以作为web应用服务器&#xff0c;开源的。 php .php tomcat .jsp nginx .html tomcat 是用java代码写的程序&#xff0c;运行的是javaweb应用程序 tomcat的特点和功能&#xff1a; 1.servlet容器…...

Python 插入、替换、提取、或删除Excel中的图片

Excel是主要用于处理表格和数据的工具&#xff0c;我们也能在其中插入、编辑或管理图片&#xff0c;为工作表增添视觉效果&#xff0c;提升报告的吸引力。本文将详细介绍如何使用Python操作Excel中的图片&#xff0c;包含以下4个基础示例&#xff1a; 文章目录 Python 在Excel…...

紧凑型建模的veriloga语句要怎么看?

说点人话&#xff0c;真传一句话&#xff0c;那些一堆公式似是而非的东西&#xff0c;都是半懂不懂的人沽名钓誉用的。 其实建模&#xff0c;归根结底明白几个东西就行了。 1.什么是你的输入和输出信号&#xff1f; 2.你对输入输出信号要建立什么功能关系&#xff1f; 那我们看…...

大语言模型系列-Transformer介绍

大语言模型系列&#xff1a;Transformer介绍 引言 在自然语言处理&#xff08;NLP&#xff09;领域&#xff0c;Transformer模型已经成为了许多任务的标准方法。自从Vaswani等人在2017年提出Transformer以来&#xff0c;它已经彻底改变了NLP模型的设计。本文将介绍Transforme…...

JavaDS —— 顺序表ArrayList

顺序表 顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构&#xff0c;一般情况下采用数组存储。在数组上完成数据的增删查改。在物理和逻辑上都是连续的。 模拟实现 下面是我们要自己模拟实现的方法&#xff1a; 首先我们要创建一个顺序表&#xff0c;顺序表…...

Sphinx 搜索配置

官方文档 http://sphinxsearch.com/docs/sphinx3.html 支持中文&#xff0c;英文&#xff0c;日文&#xff0c;韩文&#xff0c;俄罗斯语搜索 版本是 官网3.6.1版本 文件 sphinx.conf.dist 的windows 配置&#xff0c;官网下载下来后微微配置即可。 # Minimal Sphinx confi…...

如何在不关闭防火墙的情况下,让两台设备ping通

问题现象 如题&#xff0c;做虚拟机实验的时候&#xff0c;有一台linux系统的虚拟机配置的ip地址是192.168.172.181 物理主机的ip地址是192.168.172.1 此时物理主机可以ping通虚拟机 但是虚拟机不能ping通物理主机 此时我们可以想到&#xff0c;有可能是物理主机防火墙的原因。…...

windows USB 设备驱动开发-USB 等时传输

客户端驱动程序可以生成 USB 请求块 (URB) 以在 USB 设备中向/从常时等量端点传输数据。虽然USB设备一向以非等时传输出名&#xff0c;USB提供的是一种串行数据&#xff0c;而非等时&#xff0c;但是USB仍然设计了等时传输的机制&#xff0c;但根据笔者的经验&#xff0c;等时传…...

【文件共享 windows和linux】Windows Server 2016上开启文件夹共享,并在CentOS 7.4上访问和下载文件

要在Windows Server 2016上开启文件夹共享&#xff0c;并在CentOS 7.4上访问和下载文件&#xff0c;请按照以下步骤操作&#xff1a; 在Windows Server 2016上开启文件夹共享&#xff1a; 启用SMB服务&#xff1a; 打开“服务器管理器”。选择“文件和存储服务” > “共享…...

【知网CNKI-注册安全分析报告】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞…...

【Python_GUI】tkinter常用组件——文本类组件

文本时窗口中必不可少的一部分&#xff0c;tkinter模块中&#xff0c;有3种常用的文本类组件&#xff0c;通过这3种组件&#xff0c;可以在窗口中显示以及输入单行文本、多行文本、图片等。 Label标签组件 Label组件的基本使用 Label组件是窗口中比较常用的组件&#xff0c;…...

zdppy+onlyoffice+vue3解决文档加载和文档强制保存时弹出警告的问题

解决过程 第一次排查 最开始排查的是官方文档说的 https://api.onlyoffice.com/editors/troubleshooting#key 解决方案。参考的是官方的 https://github.com/ONLYOFFICE/document-server-integration/releases/latest/download/Python.Example.zip 基于Django的Python代码。 …...

C语言从头学31——与字符串变量相关的几个函数

strlen、strcpy、strcat、strcmp、sprintf这些函数都是与字符串相关的&#xff0c;除了sprintf是定义在stdio.h中外&#xff0c;其余几个都定义在string.h中&#xff0c;比较新的编译器版本stdio.h中已经含有string.h的内容&#xff0c;所以编程时不需要再包含string.h这个头文…...

Laravel批量插入数据:提升数据库操作效率的秘诀

Laravel批量插入数据&#xff1a;提升数据库操作效率的秘诀 Laravel作为PHP的现代Web应用框架&#xff0c;提供了优雅而简洁的方法来处理数据库操作。批量插入数据是数据库操作中常见的需求&#xff0c;尤其是在处理大量数据时&#xff0c;批量插入可以显著提高性能。本文将详…...

OpenCV:解锁计算机视觉的魔法钥匙

OpenCV&#xff1a;解锁计算机视觉的魔法钥匙 在人工智能与图像处理的世界里&#xff0c;OpenCV是一个响当当的名字。作为计算机视觉领域的瑞士军刀&#xff0c;OpenCV以其丰富的功能库、跨平台的特性以及开源的便利性&#xff0c;成为了开发者手中不可或缺的工具。本文将深入…...

手写简单模拟mvc

目录结构&#xff1a; 两个注解类&#xff1a; Controller&#xff1a; package com.heaboy.annotation;import java.lang.annotation.*;/*** 注解没有功能只是简单标记* .RUNTIME 运行时还能看到* .CLASS 类里面还有&#xff0c;构建对象久没来了&#xff0c;这个说明…...

蓝桥杯 2024 15届国赛 A组 儿童节快乐

P10576 [蓝桥杯 2024 国 A] 儿童节快乐 题目描述 五彩斑斓的气球在蓝天下悠然飘荡&#xff0c;轻快的音乐在耳边持续回荡&#xff0c;小朋友们手牵着手一同畅快欢笑。在这样一片安乐祥和的氛围下&#xff0c;六一来了。 今天是六一儿童节&#xff0c;小蓝老师为了让大家在节…...

Robots.txt 文件

什么是robots.txt&#xff1f; robots.txt 是一个位于网站根目录下的文本文件&#xff08;如&#xff1a;https://example.com/robots.txt&#xff09;&#xff0c;它用于指导网络爬虫&#xff08;如搜索引擎的蜘蛛程序&#xff09;如何抓取该网站的内容。这个文件遵循 Robots…...

laravel8+vue3.0+element-plus搭建方法

创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...

站群服务器的应用场景都有哪些?

站群服务器主要是为了多个网站的托管和管理所设计的&#xff0c;可以通过集中管理和高效资源的分配&#xff0c;来支持多个独立的网站同时运行&#xff0c;让每一个网站都可以分配到独立的IP地址&#xff0c;避免出现IP关联的风险&#xff0c;用户还可以通过控制面板进行管理功…...

PostgreSQL——环境搭建

一、Linux # 安装 PostgreSQL 15 仓库 sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-$(rpm -E %{rhel})-x86_64/pgdg-redhat-repo-latest.noarch.rpm# 安装之前先确认是否已经存在PostgreSQL rpm -qa | grep postgres# 如果存在&#xff0…...

Chromium 136 编译指南 Windows篇:depot_tools 配置与源码获取(二)

引言 工欲善其事&#xff0c;必先利其器。在完成了 Visual Studio 2022 和 Windows SDK 的安装后&#xff0c;我们即将接触到 Chromium 开发生态中最核心的工具——depot_tools。这个由 Google 精心打造的工具集&#xff0c;就像是连接开发者与 Chromium 庞大代码库的智能桥梁…...

Kubernetes 节点自动伸缩(Cluster Autoscaler)原理与实践

在 Kubernetes 集群中&#xff0c;如何在保障应用高可用的同时有效地管理资源&#xff0c;一直是运维人员和开发者关注的重点。随着微服务架构的普及&#xff0c;集群内各个服务的负载波动日趋明显&#xff0c;传统的手动扩缩容方式已无法满足实时性和弹性需求。 Cluster Auto…...

何谓AI编程【02】AI编程官网以优雅草星云智控为例建设实践-完善顶部-建立各项子页-调整排版-优雅草卓伊凡

何谓AI编程【02】AI编程官网以优雅草星云智控为例建设实践-完善顶部-建立各项子页-调整排版-优雅草卓伊凡 背景 我们以建设星云智控官网来做AI编程实践&#xff0c;很多人以为AI已经强大到不需要程序员了&#xff0c;其实不是&#xff0c;AI更加需要程序员&#xff0c;普通人…...

Xcode 16 集成 cocoapods 报错

基于 Xcode 16 新建工程项目&#xff0c;集成 cocoapods 执行 pod init 报错 ### Error RuntimeError - PBXGroup attempted to initialize an object with unknown ISA PBXFileSystemSynchronizedRootGroup from attributes: {"isa">"PBXFileSystemSynchro…...

如何做好一份技术文档?从规划到实践的完整指南

如何做好一份技术文档&#xff1f;从规划到实践的完整指南 &#x1f31f; 嗨&#xff0c;我是IRpickstars&#xff01; &#x1f30c; 总有一行代码&#xff0c;能点亮万千星辰。 &#x1f50d; 在技术的宇宙中&#xff0c;我愿做永不停歇的探索者。 ✨ 用代码丈量世界&…...