观察者模式:对象之间的订阅机制
欢迎来到设计模式系列的第十三篇文章!在之前的文章中,我们学习了许多常用的设计模式,今天我们将介绍观察者模式,它是一种行为型设计模式,用于定义对象之间的一对多依赖关系,当一个对象的状态发生变化时,所有依赖于它的对象都会得到通知并自动更新。
在学习观察者模式前,我们可以带着一下三个问题来学习:
1.谁是观察者,谁又是被观察者?
2.观察者如何”观察”被观察者的?
3.为什么要使用观察着模式?
4.你在工作中见过哪些观察者模式?
观察者模式简介
观察者模式是一种常用的设计模式,它用于构建对象之间的发布-订阅(Publish-Subscribe)关系。在观察者模式中,有两类核心角色:
- 主题(Subject):主题是被观察的对象,它维护了一个观察者列表,可以动态地添加或删除观察者。主题通常具有一种状态,当状态发生变化时,会通知所有观察者。
- 观察者(Observer):观察者是依赖于主题的对象,它们会注册到主题上,以便在主题的状态发生变化时得到通知并执行相应的操作。
观察者模式的核心思想是降低主题和观察者之间的耦合度,使得它们可以独立地变化。这种松耦合的设计可以更好地支持可维护性和可扩展性。
为什么需要观察者模式?
在软件开发中,经常会遇到一对多的场景,例如:
- 一个新闻网站需要通知多个订阅者(用户)有新文章发布。
- 一个股票市场应用需要通知多个投资者股票价格的变化。
- 一个气象站需要通知多个应用天气信息的变化。
如果没有观察者模式,我们可能会采用硬编码的方式来实现这些通知,但这样会导致高耦合和不易维护的问题。观察者模式通过将主题和观察者分离,使得它们可以独立变化,从而更好地应对这类场景。
观察者模式的实现
观察者模式的实现通常包括以下几个关键元素:
- 主题接口(Subject):定义了主题对象的基本操作,包括注册观察者、删除观察者和通知观察者等。
- 具体主题(ConcreteSubject):实现了主题接口,并维护了一个观察者列表。具体主题通常具有一个状态,当状态发生变化时,会通知所有注册的观察者。
- 观察者接口(Observer):定义了观察者对象的更新操作,通常包括一个
update
方法。 - 具体观察者(ConcreteObserver):实现了观察者接口,并注册到具体主题上。当主题状态发生变化时,具体观察者的
update
方法会被调用。
现在,让我们通过一个示例来演示观察者模式的实现。假设我们正在开发一个简单的股票市场应用,股票价格会不断变化,我们需要通知多个投资者股票价格的变化情况。
首先,我们定义观察者接口 Observer
:
public interface Observer {void update(double price);
}
然后,我们定义主题接口 Subject
:
public interface Subject {void registerObserver(Observer observer);void removeObserver(Observer observer);void notifyObservers();
}
接下来,我们创建一个具体主题 StockMarket
,它继承了 Subject
接口:
import java.util.ArrayList;
import java.util.List;public class StockMarket implements Subject {private List<Observer> observers = new ArrayList<>();private double price;@Overridepublic void registerObserver(Observer observer) {observers.add(observer);}@Overridepublic void removeObserver(Observer observer) {observers.remove(observer);}@Overridepublic void notifyObservers() {for (Observer observer : observers) {observer.update(price);}}public void setPrice(double price) {this.price = price;notifyObservers();}
}
在 StockMarket
类中,我们维护了一个观察者列表 observers
和股票价格 price
。当 setPrice
方法被调用时,会通知所有注册的观察者。
接下来,我们创建一个具体观察者 Investor
,它实现了 Observer
接口:
public class Investor implements Observer {private String name;public Investor(String name) {this.name = name;}@Overridepublic void update(double price) {System.out.println(name + " 收到股票价格更新,当前价格为 " + price);}
}
最后,我们可以测试观察者模式的效果:
public class Main {public static void main(String[] args) {StockMarket stockMarket = new StockMarket();Investor investor1 = new Investor("Alice");Investor investor2 = new Investor("Bob");stockMarket.registerObserver(investor1);stockMarket.registerObserver(investor2);stockMarket.setPrice(100.0);stockMarket.setPrice(105.0);stockMarket.removeObserver(investor1);stockMarket.setPrice(110.0);}
}
以上代码创建了一个股票市场 StockMarket
和两个投资者 Investor
,并演示了股票价格的变化如何通知投资者。
观察者模式的优点
观察者模式具有许多优点,使其成为软件开发中常用的设计模式之一:
- 降低耦合度:观察者模式将主题和观察者分离,主题不需要知道观察者的具体细节,从而降低了它们之间的耦合度。
- 支持广播通信:主题状态变化时,可以通知多个观察者,实现了一对多的通信,方便信息广播。
- 开闭原则:通过增加新的观察者类和主题类,可以扩展观察者模式,符合开闭原则。
- 可维护性:因为观察者和主题之间的关系是松散的,所以更容易维护和修改。
观察者模式的缺点
观察者模式也存在一些缺点,需要考虑:
- 观察者太多时性能问题:如果观察者太多,通知所有观察者可能会影响性能,尤其是在大规模系统中。
- 顺序问题:观察者的通知顺序可能不确定,如果有顺序要求,需要额外处理。
- 可能导致循环依赖:主题和观察者之间的循环依赖可能引入问题,需要小心处理。
观察者模式的应用场景
观察者模式适用于以下场景:
- 一对多的依赖关系:当一个对象的状态变化需要通知多个其他对象时,观察者模式非常适用。例如,新闻发布、股票市场更新等。
- 抽象模型与实现分离:当需要将抽象模型与其具体实现分离时,观察者模式可以帮助实现这种分离。例如,图形界面框架中的事件处理。
- 动态系统:在动态系统中,对象的数量和类型可能会随时改变,观察者模式允许动态地添加或删除观察者。
观察者模式的实际应用
观察者模式在现实世界和软件开发中都有广泛应用。以下是一些实际应用示例:
- 邮件订阅:邮件订阅服务是观察者模式的一个典型应用。用户可以订阅不同类型的邮件通知,当有新邮件到达时,订阅者会收到通知。
- 社交媒体通知:社交媒体平台可以通知用户关注的人或页面的更新,例如,新的帖子、消息或评论。
- 股票市场应用:股票市场应用通常使用观察者模式来实时通知投资者股票价格的变化。
- 事件处理:图形用户界面(GUI)框架使用观察者模式来处理用户事件,例如,鼠标点击、键盘输入等。
最佳实践
在使用观察者模式时,有一些最佳实践值得注意:
- 避免循环依赖:确保主题和观察者之间没有循环依赖,以防止潜在的问题。
- 考虑多线程情况:如果在多线程环境中使用观察者模式,确保实现线程安全的方式来处理观察者列表和状态更新。
- 谨慎使用广播通知:通知所有观察者可能会影响性能,如果只有部分观察者关心状态变化,可以考虑使用条件通知。
想进一步了解观察者模式的老铁可以了解一下 spring中的事件机制:深入理解事件发布监听机制
总结
观察者模式是一种非常有用的设计模式,用于实现对象之间的松耦合通信。通过定义一对多的依赖关系,主题状态变化时通知多个观察者,实现了对象之间的订阅机制。在实际应用中,观察者模式可以帮助我们构建灵活、可扩展的系统。
相关文章:
观察者模式:对象之间的订阅机制
欢迎来到设计模式系列的第十三篇文章!在之前的文章中,我们学习了许多常用的设计模式,今天我们将介绍观察者模式,它是一种行为型设计模式,用于定义对象之间的一对多依赖关系,当一个对象的状态发生变化时&…...

【1462. 课程表 IV】
来源:力扣(LeetCode) 描述: 你总共需要上 numCourses 门课,课程编号依次为 0 到 numCourses-1 。你会得到一个数组 prerequisite ,其中 prerequisites[i] [ai, bi] 表示如果你想选 bi 课程,你…...

Kerberos 身份验证
简介 Kerberos 是一种由 MIT(麻省理工大学)提出的一种基于加密 Ticket 的身份认证协议。它旨在通过使用密钥加密技术为客户端/服务器应用程序提供强身份验证,用于验证用户或主机的标识。。 适用范围:Windows Server 2022、Window…...

R语言贝叶斯METROPOLIS-HASTINGS GIBBS 吉布斯采样器估计变点指数分布分析泊松过程车站等待时间...
原文链接:http://tecdat.cn/?p26578 指数分布是泊松过程中事件之间时间的概率分布,因此它用于预测到下一个事件的等待时间,例如,您需要在公共汽车站等待的时间,直到下一班车到了(点击文末“阅读原文”获取…...

通付盾入选2023年度“上市苗圃工程”重点企业
近日,2023年度苏州工业园区企业上市苗圃工程认定名单公示,江苏通付盾科技有限公司成功入选园区“上市苗圃工程”重点企业。 2023年第一批次苗圃企业认定结果: 企业上市苗圃工程 上市企业是衡量地方综合经济实力的重要标尺,也是区…...
SpringMVC之文件上传下载
SpringMVC是一个基于Java的Web框架,它提供了一套用于构建Web应用程序的开发模型。在SpringMVC中,文件上传和下载是常见的功能之一。 SpringMVC文件上传和下载的介绍: 介绍文件上传: 在SpringMVC中,文件上传功能可以通…...

嵌入式IDE(2):KEIL中SCF分散加载链接文件详解和实例分析
在上一篇文章IAR中ICF链接文件详解和实例分析中,我通过I.MX RT1170的SDK中的内存映射关系,分析了IAR中的ICF链接文件的语法。对于MCU编程所使用的IDE来说,IAR和Keil用得比较多,所以这一篇文章就来分析一下Keil的分散文件.scf(scat…...
Linux防火墙常用操作及端口开放
Linux防火墙常用操作及端口开放 1.查看防火墙状态 firewall-cmd --state 2.开启防火墙 systemctl start firewalld.service 3.开启指定端口 firewall-cmd --zonepublic --add-port3306/tcp --permanent firewall-cmd --zonepublic --add-port6379/tcp --permanent 显示success表…...
[JAVAee]Linux上的javax.mail报错
我们把在window写的项目部署到Linux上的Tomcat时,如果发现使用不了了,该如何找到错误呢?找到报错的地方在哪呢? 在Linux环境下来到Tomcat目录下的logs目录,输入: tail -f catalina.out -n 500 tail 就是把文件的末尾几行读取到终端上,并会持续刷新 -f 循环读取 catalina.ou…...

开学季|校园迎新哪家强?VR全景来导航
九月开学迎新季,各大高校的迎新活动开展的如火如荼,随着科技的不断进步,高校为了更好的开展迎新活动,让新生们尽快熟悉新的校园和生活,会利用VR全景技术带领着新生进行校园游览,给予新生们巨大便利的同时&a…...

el-checkbox-group限制勾选数量
<!--* Description: 视频监控 页面* Author: mhf* Date: 2023-08-15 13:26:33 --> <template><div class"videoSurveillance"><el-row :gutter"24"><el-col :span"4"><div class"videoSurveillance-left&…...

【JavaScript】WebAPI入门到实战
文章目录 一、WebAPI背景知识1. 什么是WebAPI?2. 什么是API? 二、DOM基本概念三、获取元素三、事件初识1. 点击事件2. 键盘事件 四、操作元素1. 获取/修改元素内容2. 获取/修改元素属性3. 获取/修改表单元素属性4. 获取/修改样式属性 五、操作节点1. 新增…...

奥康的高尔夫鞋,圈不住投资者的心
文 | 螳螂观察 作者 | 青月 鞋服行业终于熬过了“寒冬”,2023年行业景气度开始逐步回暖。 东方财富Choice数据显示,截至8月17日,已有28家鞋帽服装类上市公司发布了2023年中期业绩预告或快报,其中,9家预增࿰…...

vue2配置环境变量并且nginx运行成功
需求:我在vue项目配置了生产环境和开发环境,之后通过proxy代理的方式把地址转发到真实的服务器地址上用于请求接口,之后把项目打包后上传到nginx上,之后接口报错404,但是本地运行是可以访问的,找了很久终于…...
Java+Swing形成GUI图像界面
一、Swing 简介 Swing 主要用来开发 GUI 程序,GUI(Graphical User Interface)即图形用户界面。Java 中针对 GUI 设计提供了丰富的类库,这些类分别位于 java.awt 和 java.swing 中,简称 AWT 和 Swing ;其中,AWT(Abstract Window Toolkit)是抽象窗口工具包,是 Java 平…...
编辑距离 -- 动规
72. 编辑距离 给出动规的两种常见实现形式:自顶向下、自底向上,前者一般借助递归函数备忘录实现,后者通常基于dp数组实现。 class MinDistance:"""72. 编辑距离https://leetcode.cn/problems/edit-distance/""&quo…...
douyin【商品抢购js脚本】
文章目录 前言订阅须知知识点源码前言 脚本主要用来实现抢购douyin商城、直播间秒杀商品等一系列商品 订阅须知 订阅后,只提供js源代码,不提供教学,请根据源码自行抓包知识点 1、在查询串插入一个固定的键rstr 2、对查询串进行按键排序并取值,对空格和+进行转义为a …...

常见Web安全技术总结!474页Web安全从入门到精通(附PDF)
Web安全范围比较大,知识点比较杂,很多朋友都无从下手,这不可怕,可怕的是乱下手,其实往往基础才是决定你是否能走远的关键。 为了帮助大家入门网安,给大家推荐一份《新手Web安全入门到精通》,共…...

Prometheus 监控指南:如何可靠地记录数字时间序列数据
🌷🍁 博主猫头虎(🐅🐾)带您 Go to New World✨🍁 🐅🐾猫头虎建议程序员必备技术栈一览表📖: 🛠️ 全栈技术 Full Stack: 📚…...

rsync远程同步+inotify监控
目录 一、Rsync 简介 1、rsync是什么 2、备份的方式 3、rsync同步方式 4、常用rsync命令 5、配置源的两种表达方法 二、rsync实验 1、本地复制 编辑编辑 2、异地复制 2.1 rsync服务器配置 2.2 rsync客户端配置 2.2.1 普通同步 2.2.2 免密同步 2.2.3 --delet…...
大学生职业发展与就业创业指导教学评价
这里是引用 作为软工2203/2204班的学生,我们非常感谢您在《大学生职业发展与就业创业指导》课程中的悉心教导。这门课程对我们即将面临实习和就业的工科学生来说至关重要,而您认真负责的教学态度,让课程的每一部分都充满了实用价值。 尤其让我…...

OPenCV CUDA模块图像处理-----对图像执行 均值漂移滤波(Mean Shift Filtering)函数meanShiftFiltering()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 在 GPU 上对图像执行 均值漂移滤波(Mean Shift Filtering),用于图像分割或平滑处理。 该函数将输入图像中的…...
2023赣州旅游投资集团
单选题 1.“不登高山,不知天之高也;不临深溪,不知地之厚也。”这句话说明_____。 A、人的意识具有创造性 B、人的认识是独立于实践之外的 C、实践在认识过程中具有决定作用 D、人的一切知识都是从直接经验中获得的 参考答案: C 本题解…...

RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)
RabbitMQ 一、RabbitMQ概述 RabbitMQ RabbitMQ最初由LShift和CohesiveFT于2007年开发,后来由Pivotal Software Inc.(现为VMware子公司)接管。RabbitMQ 是一个开源的消息代理和队列服务器,用 Erlang 语言编写。广泛应用于各种分布…...

Chromium 136 编译指南 Windows篇:depot_tools 配置与源码获取(二)
引言 工欲善其事,必先利其器。在完成了 Visual Studio 2022 和 Windows SDK 的安装后,我们即将接触到 Chromium 开发生态中最核心的工具——depot_tools。这个由 Google 精心打造的工具集,就像是连接开发者与 Chromium 庞大代码库的智能桥梁…...

c++第七天 继承与派生2
这一篇文章主要内容是 派生类构造函数与析构函数 在派生类中重写基类成员 以及多继承 第一部分:派生类构造函数与析构函数 当创建一个派生类对象时,基类成员是如何初始化的? 1.当派生类对象创建的时候,基类成员的初始化顺序 …...

软件工程 期末复习
瀑布模型:计划 螺旋模型:风险低 原型模型: 用户反馈 喷泉模型:代码复用 高内聚 低耦合:模块内部功能紧密 模块之间依赖程度小 高内聚:指的是一个模块内部的功能应该紧密相关。换句话说,一个模块应当只实现单一的功能…...
【Kafka】Kafka从入门到实战:构建高吞吐量分布式消息系统
Kafka从入门到实战:构建高吞吐量分布式消息系统 一、Kafka概述 Apache Kafka是一个分布式流处理平台,最初由LinkedIn开发,后成为Apache顶级项目。它被设计用于高吞吐量、低延迟的消息处理,能够处理来自多个生产者的海量数据,并将这些数据实时传递给消费者。 Kafka核心特…...

02.运算符
目录 什么是运算符 算术运算符 1.基本四则运算符 2.增量运算符 3.自增/自减运算符 关系运算符 逻辑运算符 &&:逻辑与 ||:逻辑或 !:逻辑非 短路求值 位运算符 按位与&: 按位或 | 按位取反~ …...
WEB3全栈开发——面试专业技能点P4数据库
一、mysql2 原生驱动及其连接机制 概念介绍 mysql2 是 Node.js 环境中广泛使用的 MySQL 客户端库,基于 mysql 库改进而来,具有更好的性能、Promise 支持、流式查询、二进制数据处理能力等。 主要特点: 支持 Promise / async-await…...