Java观察者模式源码剖析及使用场景

观察者模式

  • 一、原理及通俗理解
  • 二、股票项目中使用
  • 三、Spring框架中使用观察者模式
  • 四、总结优缺点以及使用经验
    • 一、优点
    • 二、缺点
    • 三、使用经验

一、原理及通俗理解

观察者模式是一种行为设计模式,它定义了对象之间的一对多依赖关系,使得一个对象的状态发生变化时,其所有依赖者都会收到通知并自动更新。

观察者模式涉及两个主要角色:Subject(主题)和Observer(观察者)。

  • Subject:维护一个观察者列表,提供注册和移除观察者的方法,并在自身状态发生变化时,通知所有已注册的观察者。
  • Observer:对感兴趣的事物(Subject)的状态变化做出相应的反应。

就像订阅了某个博客的通知一样,当博客作者发布新文章时,所有订阅者都会收到通知。在这里,博客作者就是主题,而订阅者则是观察者。

二、股票项目中使用

需求描述:假设我们有一个股票交易系统,需要实现对股票价格变化的实时通知功能。当股票价格发生变化时,所有订阅该股票的用户应该能够接收到最新的股票价格信息。

  1. 定义主题接口(Subject) 包含注册观察者、移除观察者和通知观察者的方法
import java.util.ArrayList;
import java.util.List;public interface Subject {void registerObserver(Observer observer);void removeObserver(Observer observer);void notifyObservers(String stockSymbol, double newPrice);
}
  1. 定义观察者接口(Observer) 包含更新股票价格的方法。
public interface Observer {void update(String stockSymbol, double newPrice);
}
  1. 实现具体的主题(StockExchange) StockExchange类实现了Subject接口,扮演股票交易所的角色。它维护了一个观察者列表、股票列表和股票价格列表。registerObserverremoveObserver方法用于注册和移除观察者。股票价格变化notifyObservers方法通知所有已注册的观察者。
public class StockExchange implements Subject {private List<Observer> observers; // 观察者列表private List<String> stocks; // 股票列表private List<Double> prices; // 股票价格public StockExchange() {observers = new ArrayList<>();stocks = new ArrayList<>();prices = new ArrayList<>();}//注册观察者@Overridepublic void registerObserver(Observer observer) {observers.add(observer);}//移除观察者@Overridepublic void removeObserver(Observer observer) {observers.remove(observer);}//股票价格变化,通知所有已注册的观察者。@Overridepublic void notifyObservers(String stockSymbol, double newPrice) {for (Observer observer : observers) {observer.update(stockSymbol, newPrice);}}// 添加新的股票和初始价格public void addStock(String stockSymbol, double initialPrice) {stocks.add(stockSymbol);prices.add(initialPrice);}// 更新股票价格并通知观察者public void updateStockPrice(String stockSymbol, double newPrice) {int index = stocks.indexOf(stockSymbol);if (index != -1) {prices.set(index, newPrice);notifyObservers(stockSymbol, newPrice);} else {System.out.println("Stock not found: " + stockSymbol);}}
}
  1. 实现具体的观察者(InvestorClient) InvestorClient类实现了Observer接口,扮演投资者的角色。它会在收到股票价格更新时打印出通知信息。
public class InvestorClient implements Observer {private String investorName;public InvestorClient(String investorName) {this.investorName = investorName;}//股票价格更新时打印出通知信息。@Overridepublic void update(String stockSymbol, double newPrice) {System.out.println("Investor " + investorName + " received update: " + stockSymbol + " new price is $" + newPrice);}
}
  1. 使用示例main方法中,我们创建了一个StockExchange实例,添加了一些股票。然后创建了两个InvestorClient实例并注册为观察者。
public class StockTradingApp {public static void main(String[] args) {// 创建股票交易所StockExchange exchange = new StockExchange();// 添加股票exchange.addStock("AAPL", 120.0);exchange.addStock("GOOG", 2500.0);exchange.addStock("MSFT", 280.0);// 创建投资者客户端InvestorClient investor1 = new InvestorClient("John");InvestorClient investor2 = new InvestorClient("Jane");// 注册观察者exchange.registerObserver(investor1);exchange.registerObserver(investor2);// 更新股票价格exchange.updateStockPrice("AAPL", 125.5);exchange.updateStockPrice("GOOG", 2520.0);// 移除观察者exchange.removeObserver(investor2);// 再次更新股票价格exchange.updateStockPrice("MSFT", 290.0);}
}
  1. 输出: 模拟了几次股票价格更新,观察者会收到相应的通知,最后,我们移除了一个观察者,再次更新股票价格时,只有剩余的观察者会收到通知。
Investor John received update: AAPL new price is $125.5
Investor Jane received update: AAPL new price is $125.5
Investor John received update: GOOG new price is $2520.0
Investor Jane received update: GOOG new price is $2520.0
Investor John received update: MSFT new price is $290.0

三、Spring框架中使用观察者模式

  1. 在 Spring 的事件机制中,主要涉及以下几个核心组件:
  • ApplicationEvent: 事件的基类,扮演观察者模式中的主题(Subject)角色。
  • ApplicationListener:事件监听器接口,扮演观察者(Observer)角色,用于处理事件。
  • ApplicationEventMulticaster:事件广播器,负责将事件分发给注册的监听器。
  1. 主题角色,ApplicationEvent 继承自 EventObject类,EventObject 就是观察者模式中的主题,它维护了一个 source 属性,表示事件源,即产生事件的对象。
public abstract class ApplicationEvent extends EventObject {// .../** Helper constructor for subclasses. */public ApplicationEvent(Object source) {super(source);}// ...
}
  1. 观察者角色,ApplicationListener 接口扩展自 EventListener,它定义了一个 onApplicationEvent 方法,用于处理传入的事件。每个事件监听器都需要实现这个接口,并编写处理逻辑。
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {void onApplicationEvent(E event);
}
  1. ApplicationEventMulticaster 接口定义了 multicastEvent 方法,用于将事件广播给所有注册的监听器。
public interface ApplicationEventMulticaster {void multicastEvent(ApplicationEvent event);void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType);// ...
}
  1. Spring 提供了一个默认实现 SimpleApplicationEventMulticaster,它维护了一个 ApplicationListener 列表,在接收到事件时,会遍历这个列表,依次通知每个监听器。
public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {// ...@Overridepublic void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));Executor executor = getTaskExecutor();for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {// ...if (executor != null) {executor.execute(() -> invokeListener(listener, event));} else {invokeListener(listener, event);}}}private void invokeListener(ApplicationListener listener, ApplicationEvent event) {try {listener.onApplicationEvent(event);}catch (ClassCastException ex) {// ...}}// ...
}

7.在Spring框架中,观察者模式被广泛应用于事件监听和发布/订阅机制。使用观察者模式来实现事件驱动的编程,允许不同组件之间进行松耦合的通信。Spring中的事件(Event)是指应用程序中发生的某种特定状态的变化,比如应用启动、上下文刷新、HTTP请求处理等。当这些事件发生时,可以通过观察者模式将相关的监听器得到通知并做出响应。

  • Spring 容器在初始化和销毁时会发布相应的事件,比如 ContextRefreshedEvent 和 ContextClosedEvent。
  • Spring MVC 在处理请求过程中会发布 RequestHandledEvent 等事件。
  • Spring Data 在执行数据库操作时会发布 AfterSaveEvent 等事件。
  • 可以自定义事件和监听器,并将其注册到 Spring 容器中,实现自己的事件驱动逻辑。通过观察者模式,Spring 为我们提供了一种非常灵活和可扩展的编程模型。

四、总结优缺点以及使用经验

一、优点

  1. 解耦合:观察者模式将主题和观察者解耦,主题不需要知道观察者的具体实现,只需要维护一个观察者列表并通知它们。
  2. 可扩展性好:可以方便地添加新的观察者,而无需修改主题代码。
  3. 支持广播通信:主题可以向多个观察者对象发送通知,实现一对多的通信模式。

二、缺点

  1. 有可能出现循环依赖的问题:如果单个观察者对象被注册到一个或多个主题对象中,当主题状态发生变化时,可能会导致循环调用,使程序陷入死循环。
  2. 误操作风险:观察者需要正确地维护主题和观察者之间的注册关系,否则可能会出现观察者无法接收到通知或接收到重复通知的情况。

三、使用经验

  1. 适当使用:观察者模式适用于主题和观察者之间存在一对多的依赖关系,且需要实现自动通知机制的场景。但如果关系是一对一的,或者通知机制不是必需的,则不需要使用观察者模式,可以考虑使用其他设计模式。
  2. 维护注册关系:注意正确地注册和移除观察者,防止出现观察者无法接收到通知或接收到重复通知的情况。
  3. 避免循环依赖:如果存在循环依赖的风险,需要在设计时加以考虑,避免出现死循环的情况。
  4. 合理分配职责:主题负责维护观察者列表和通知,观察者负责响应通知并执行相应的操作。不要在主题或观察者中加入过多的逻辑,保持职责分离。
  5. 考虑性能:如果观察者数量较多,通知所有观察者可能会带来一定的性能开销。在必要时,可以考虑使用异步通知或者引入事件队列等机制来优化性能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/735714.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

7、Copmose自定义颜色和主题切换

Copmose自定义颜色和主题切换 一起颜色的设置的都是在res/values/colors里面去做颜色&#xff0c; 但是当使用compose的时候&#xff0c;抛弃了使用了ui.theme底下的Color.kt和Theme.kt 但是默认使用的是MaterialTheme主题&#xff0c;里面的颜色字段不能定义&#xff0c;因此…

重写单链表的快速排序

2018年第一次试着写单链表的快速排序。所使用的方法虽然代码非常简洁&#xff0c;只有20行&#xff0c;但可惜并不是纯正的快速排序&#xff0c;而且使用的是数据交换也不是节点链接改变&#xff0c;造成效率也有点问题。后来又于2022年重写单链表的快速排序。这一次想出了一种…

贪吃蛇(C语言实现)

贪食蛇&#xff08;也叫贪吃蛇&#xff09;是一款经典的小游戏。 —————————————————————— 本博客实现使用C语言在Windows环境的控制台中模拟实现贪吃蛇小游戏。 实行的基本功能&#xff1a; • 贪吃蛇地图的绘制 • 蛇吃食物的功能&#xff08;上、…

详解DNS服务

华子目录 概述产生原因作用连接方式 因特网的域名结构拓扑分类域名服务器类型划分 DNS域名解析过程分类解析图图过程分析注意 搭建DNS域名解析服务器概述安装软件bind服务中的三个关键文件 配置文件分析主配置文件共4部分组成区域配置文件作用区域配置文件示例分析正向解析反向…

动态规划 Leetcode 70 爬楼梯

爬楼梯 Leetcode 70 学习记录自代码随想录 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 示例 1&#xff1a; 输入&#xff1a;n 2 输出&#xff1a;2 解释&#xff1a;有两种方法可以爬到…

SpringCloud 微服务架构编码构建

一、前言 接下来是开展一系列的 SpringCloud 的学习之旅&#xff0c;从传统的模块之间调用&#xff0c;一步步的升级为 SpringCloud 模块之间的调用&#xff0c;此篇文章为第一篇&#xff0c;即不使用 SpringCloud 组件进行模块之间的调用&#xff0c;后续会有很多的文章循序渐…

️ IP代理实操指南:如何在爬虫项目中避免封禁和限制 ️‍♂️

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

moreutils yum安装

在基于RPM的Linux发行版中&#xff0c;如CentOS、Red Hat Enterprise Linux或Fedora&#xff0c;你可以使用yum&#xff08;在较旧版本中&#xff09;或dnf&#xff08;在较新版本中&#xff09;来安装moreutils软件包。不过需要注意的是&#xff0c;默认的官方仓库可能并未包含…

企业战略管理 找准定位 方向 使命 边界 要干什么事 要做多大的生意 资源配置投入

AI突破千行百业&#xff0c;也难打破护城河 作为每个企业或个人的立命生存之本&#xff0c;有的企业在某个领域长期努力筑起了高高的护城河。 战略是什么&#xff1f;用处&#xff0c;具体内容 企业战略是指企业为了实现长期目标&#xff0c;制定的总体规划和长远发展方向。…

通过Forms+Automate+Lists+审批,实现用车申请流程

因为Sham公司目前用的用车申请流程是使用的K2系统&#xff0c;用户申请后&#xff0c;我们还需要单独另行输入Excel来汇总申请记录&#xff0c;当然K2也能导出&#xff0c;但是需要每次导出也是很麻烦的&#xff0c;而且不灵活。 刚好最近发现Forms与Automate能联通&#xff0…

华容道问题求解_详细设计(六)之简单互动和动画

简单互动 为了增加趣味性&#xff0c;增加了简单的互动功能&#xff0c;即实现了一个简单的华容道游戏。在HrdGame中有两个鼠标操作的函数&#xff0c;在传入的控件中调用这个两个函数就可以了。 代码如下&#xff1a; Click事件 private void pnl_GameBoard_MouseClick(objec…

华为OD面试分享9(2024年)

1.3 告知简历筛选通过 1.8 资格面,就简单问了一下gap原因,离职原因,期望薪水,还问了一下技术栈 这期间本来在准备机试,结果机试我上半年考了一次,但是后面没去od,hr告知成绩好像还有效就没有重新机试。具体题目忘了。 1.17 技术一面 上来先自我介绍,然后问了一下上…

Java服务器-Disruptor使用注意

最近看了一下部署后台的服务器状况&#xff0c;发现我的一个Java程序其占用的CPU时长超过100%&#xff0c;排查后发现竟是Disruptor引起的&#xff0c;让我们来看看究竟为什么Disruptor会有这样的表现。 发现占用CPU时间超过100%的进程 首先是在服务器上用top命令查看服务器状…

C语言 寻找单身狗(2个

此题知识&#xff1a;a^0a;a^a0;传值调用和传址调用要分清作用 题目&#xff1a;在 1&#xff0c;2&#xff0c;3&#xff0c;4&#xff0c;5&#xff0c;6&#xff0c;1&#xff0c;3&#xff0c;4&#xff0c;5这几个数字中找出只出现一次的数字并打印在屏幕上 解题思路&…

vue,provide和inject,备忘

在Vue.js应用中&#xff0c;provide 和 inject 是一对API&#xff0c;用于实现组件间的跨层级依赖注入。provide 在父组件中定义要向下传递的属性或方法&#xff0c;而 inject 在子组件中声明它需要从祖先组件那里注入的属性。 具体到您的例子&#xff1a; // 在根组件或任意…

超越基础:提升你的数据采集策略与IP代理的高级应用

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

【AI辅助研发】-趋势:大势已来,行业变革

【AI辅助研发】-趋势&#xff1a;大势已来&#xff0c;行业变革 引言 在科技日新月异的今天&#xff0c;人工智能&#xff08;AI&#xff09;技术已逐渐渗透到各行各业&#xff0c;其中软件研发行业更是受益匪浅。AI辅助研发已成为大势所趋&#xff0c;不仅提高了软件开发的效…

鸿蒙Harmony应用开发—ArkTS声明式开发(通用属性:动态属性设置)

动态设置组件的属性&#xff0c;支持开发者在属性设置时使用if/else语法&#xff0c;且根据需要使用多态样式设置属性。 说明&#xff1a; 从API Version 11开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 attributeModifier attributeMo…

js之原型链

在JavaScript中&#xff0c;原型链是一种用于实现继承和属性查找的机制。每个对象都有一个内部属性[[Prototype]]&#xff0c;这个属性指向创建该对象时使用的构造函数的“prototype"属性。对象的方法和属性定义在它的原型对象上。 1.原型&#xff08;Prototypes&#xf…

RHCE——一、OpenEuler22.03安装部署及例行性任务

RHCE 一、OpenEuler22.03安装部署及例行性任务 一、网络服务1.准备工作2、RHEL9操作系统的安装部署3、配置并优化OpenEuler22.034、网络配置实验&#xff1a;修改网络配置 二、例行性工作1、 单一执行的例行性任务&#xff1a;at&#xff08;一次性&#xff09;at命令详解 2、循…