观察者模式学习
观察者模式(Observer Design Pattern)也被称为发布订阅模式(Publish-Subscribe Design Pattern)。在 GoF 的《设计模式》一书中,它的定义是这样的:
Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
中文翻译:在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会自动收到通知。
一般情况下,被依赖的对象叫作被观察者(Observable),依赖的对象叫作观察者(Observer)。不过,在实际的项目开发中,这两种对象的称呼是比较灵活的,有各种不同的叫法,比如:Subject-Observer、Publisher-Subscriber、Producer-Consumer、EventEmitter-EventListener、Dispatcher-Listener。不管怎么称呼,只要应用场景符合刚刚给出的定义,都可以看作观察者模式。
从定义中可以知道,观察者主要应用于通知的场景,并且通知的对象可能需要频繁的改变。它可以解耦被观察者与观察者。
观察者模式的应用场景非常广泛,小到代码层面的解耦,大到架构层面的系统解耦,再或者一些产品的设计思路,都有这种模式的影子,比如,邮件订阅、RSS Feeds,本质上都是观察者模式。
经典实现方式
我们先来看其中最经典的一种实现方式。这也是在讲到这种模式的时候,很多书籍或资料给出的最常见的实现方式。具体的代码如下所示:
public interface Subject {void registerObserver(Observer observer);void removeObserver(Observer observer);void notifyObservers(Message message);
}public interface Observer {void update(Message message);
}public class ConcreteSubject implements Subject {private List<Observer> observers = new ArrayList<Observer>();@Overridepublic void registerObserver(Observer observer) {observers.add(observer);}@Overridepublic void removeObserver(Observer observer) {observers.remove(observer);}@Overridepublic void notifyObservers(Message message) {for (Observer observer : observers) {observer.update(message);}}}public class ConcreteObserverOne implements Observer {@Overridepublic void update(Message message) {//TODO: 获取消息通知,执行自己的逻辑...System.out.println("ConcreteObserverOne is notified.");}
}public class ConcreteObserverTwo implements Observer {@Overridepublic void update(Message message) {//TODO: 获取消息通知,执行自己的逻辑...System.out.println("ConcreteObserverTwo is notified.");}
}public class Demo {public static void main(String[] args) {ConcreteSubject subject = new ConcreteSubject();subject.registerObserver(new ConcreteObserverOne());subject.registerObserver(new ConcreteObserverTwo());subject.notifyObservers(new Message());}
}
这个例子很好理解:
它具有一个被观察者接口(Subject)和一个观察者接口(Observer);
所有具体的被观察者都需要实现Subject接口,即所有的被观察者都具有注册观察者方法(registerObserver)、删除观察者方法(removeObserver)、通知观察者方法(notifyObservers);
所有具体的观察者都需要实现Observer接口,即所有的观察者都具有执行通知逻辑的方法(update);
在被观察者中通过一个List来保存所有注册的观察者,在需要时通过遍历List来通知所有的观察者。
但上面的代码算是观察者模式的“模板代码”,只能反映大体的设计思路。在真实的软件开发中,并不需要照搬上面的模板代码。而且Java中也有一些第三方框架供我们使用,它们提供了实现观察者模式的骨架代码,我们可以基于此框架,非常容易地在自己的业务场景中实现观察者模式,不需要从零开始开发,如Guava 的EventBus、Spring Event等。
观察者模式的实现原理分析
如何通知观察者?
我们看到,被观察者需要通知每一个观察者,所以上面示例中被观察者通过一个List来保存了观察者,然后通过遍历List来实现通知每一个观察者。当然通过其他的容器也可以实现,比如Guava的EventBus就是用ConcurrentMap来存储的观察者信息的。
还有被观察者中保存的是观察者的接口,并不知道观察者的细节,即被观察者和观察者之间用松耦合方式结合(loosecoupling),符合“为交互对象之间的松耦合设计而努力”的设计原则。
在上面示例中是被观察者主动将消息推送到观察者中的,这种方式被称为推(push)。还有一种称为拉(pull)的方式,即被观察者通知观察者后,观察者再从被观察者中获取所需的数据。pull方式的前提条件是,被观察者需要提供对外获取数据的接口,同时观察者中需要能获取到被观察者对象。显然,pull方式使被观察者与观察者联系更紧密了,这不一定是一个好的现象
如何更具有复用性?
被观察者需要提供注册、通知等通用接口,其实这些操作和我们实际业务关系不大,可以考虑抽取出来。此时有两种可以考虑的方式,
一种是继承方式,将通用的操作都抽取到父类中,所有的被观察者只需要继承父类就可以具有注册、通知等操作的实现,JDK1.0中的观察者模式就是这样做的,它提供了Observable类用于给被观察者继承。
一种是组合方式,将通用的操作都抽取到其他类中,被观察者通过委托该类来实现注册、通知等通用操作。如果使用Guava的EventBus,就是通过组合EventBus类来实现的。
我们都知道组合的方式优于继承,因为继承有诸多限制,比如单继承。所以一般都推荐使用组合的方式,目前常用的第三方框架采用的都是组合的方式。
同步通知与异步通知
在通知观察者的方式上我们可以有两种方式,同步阻塞的实现方式和异步非阻塞的实现方式。
同步阻塞的实现方式
观察者和被观察者代码在同一个线程内执行,被观察者一直阻塞,直到所有的观察者代码都执行完成之后,才执行后续的代码。
上面讲到的用户注册的例子就是同步阻塞方式,register() 函数依次调用执行每个观察者的 handleRegSuccess() 函数,等到都执行完成之后,才会返回结果给客户端。
异步非阻塞的实现方式
如果注册接口是一个调用比较频繁的接口,对性能非常敏感,希望接口的响应时间尽可能短,那我们可以将同步阻塞的实现方式改为异步非阻塞的实现方式,以此来减少响应时间。
实现异步非阻塞的方式我们既可以自己手动创建线程来实现,也可以采用第三方框架提供的异步执行功能,如如Guava 的EventBus、Spring Event等。
跨进程间的调用
我们一般所介绍的观察者模式都是指在一个进程间(通常就是指同一个项目代码)进行的通知,但其实观察者模式也可以应用于进程间,比如如果用户注册成功之后,我们需要发送用户信息给大数据征信系统,而大数据征信系统是一个独立的系统,跟它之间的交互是跨不同进程的。
要实现跨进程的观察者模式,其实就是要实现进程间的通讯,这个我们就比较熟悉了,常用的就是:
RPC 接口调用的方式。
消息队列的方式。(采用消息队列的发布-订阅模式)
消息队列方式相对于RPC方式来说,被观察者和观察者解耦更加彻底,两部分的耦合更小,因为被观察者完全不感知观察者,同理,观察者也完全不感知被观察者。不过弊端就是需要引入一个新的系统(消息队列),增加了维护成本。
相关文章:
观察者模式学习
观察者模式(Observer Design Pattern)也被称为发布订阅模式(Publish-Subscribe Design Pattern)。在 GoF 的《设计模式》一书中,它的定义是这样的: Define a one-to-many dependency between objects so th…...

人工智能_机器学习078_聚类算法_概念介绍_聚类升维_降维_各类聚类算法_有监督机器学习_无监督机器学习---人工智能工作笔记0118
首先看一下什么是聚类,我们可以进入sklearn的官网去看看 可以看到这里,首先classification 这个分类我们学完了,然后就是regression回归我们也学完了对吧,其实我们现实生活中的,大部分问题就是 这两种问题就可以解决了. 然后我们再来看一个: clustering,这个就是聚类对吧.聚类算…...

基于AR+地图导航的景区智慧导览设计
随着科技的飞速发展,智慧旅游已经成为现代旅游业的一个重要趋势。在这个背景下,景区智慧导览作为智慧旅游的核心组成部分,正逐渐受到越来越多游客的青睐。本文将深入探讨地图导航软件在景区智慧导览中的应用,并分析其为游客和景区…...
git基本指令
下载代码 git clone http://.......设置分支 git checkout 分支名查询当前分支 git checkout打开终端或命令行窗口,进入你要操作的项目目录,执行以下命令,列出所有的分支,这会列出当前代码仓库中的所有分支,用带星号…...
ECMAScript基础入门
ECMAScript(简称ES)是一种标准化了的高级编程语言,它是JavaScript语言的标准化版本,由Ecma International组织发布。ECMAScript描述了JavaScript的语法和核心特性,而JavaScript是实现ECMAScript标准的编程语言。随着We…...

神经网络介绍
目录 知识点介绍 知识点介绍 前馈神经网络:(前馈网络的数据只向一个方向传播) RNN循环神经网络,下图中多个 RNN 层都是“同一个层”,这一点与之前的神经网络是不一样的。...
CPU亲和性和NUMA架构
何为CPU的亲和性 CPU的亲和性,进程要在某个给定的 CPU 上尽量长时间地运行而不被迁移到其他处理器的倾向性,进程迁移的频率小就意味着产生的负载小。亲和性一词是从affinity翻译来的,实际可以称为CPU绑定。 在多核运行的机器上,…...

目标检测-Two Stage-Fast RCNN
文章目录 前言一、Fast RCNN的网络结构和流程二、Fast RCNN的创新点1.特征提取分类回归合一2.更快的训练策略 总结 前言 前文目标检测-Two Stage-SPP Net中提到SPP Net的主要缺点是: 分开训练多个模型困难且复杂尽管比RCNN快10-100倍,但仍然很慢SPP Ne…...
vol----随记!!!
目录 一、代码生成1.先新建一个功能的对应的代码配置各项解释: 2.后设置配置菜单3.再点保存,生成vue页面,生成model,生成业务类4.再通过菜单设置编写系统菜单 一、代码生成 1.先新建一个功能的对应的代码配置 各项解释ÿ…...
vue中样式动态绑定写法
绑定样式: class样式 写法:class"xxx"xXX可以是字符串、对象、数组。 字符串写法适用于:类名不确定,要动态获取。 对象写法适用于:要绑定多个样式,个数不确定,名字也不确定。 数组写法适用于:要绑定多个样式,个数确定,…...

C语言—每日选择题—Day63
指针相关博客 打响指针的第一枪:指针家族-CSDN博客 深入理解:指针变量的解引用 与 加法运算-CSDN博客 第一题 1. 设C语言中,一个int型数据在内存中占2个字节,则unsigned int型数据的取值范围为 A:0~255 B:0…...
Mac_通过chmod处理文件权限
chmod 简介 chmod 是一个 Unix 和类 Unix 系统中的命令,用于更改文件或目录的权限。chmod 的名称来源于 “change mode”,它允许用户修改文件或目录的读取(read)、写入(write)和执行(execute&a…...
实战指南:使用 Spring Cloud Stream 集成 Kafka 构建高效消息驱动微服务
实战指南:使用 Spring Cloud Stream 集成 Kafka 构建高效消息驱动微服务 视频地址: Stream为什么被引入-尚硅谷SCS-1-内容介绍-图灵诸葛 官方文档: Spring Cloud Stream 什么是 Spring Cloud Stream? Spring Cloud Stream(SCS) 是一个用于构…...

线性代数基础【3】向量
第一节 向量的概念与运算 一、基本概念 ①向量 ②向量的模(长度) ③向量的单位化 ④向量的三则运算 ⑤向量的内积 二、向量运算的性质 (一)向量三则运算的性质 α β β αα (β γ) (α β) γk (α β) kα kβ(k l) α kα lα (二)向量内积运…...

Spring Boot + MinIO 实现文件切片极速上传技术
文章目录 1. 引言2. 文件切片上传简介3. 技术选型3.1 Spring Boot3.2 MinIO 4. 搭建Spring Boot项目5. 集成MinIO5.1 配置MinIO连接信息5.2 MinIO配置类 6. 文件切片上传实现6.1 控制器层6.2 服务层6.3 文件切片上传逻辑 7. 文件合并逻辑8. 页面展示9. 性能优化与拓展9.1 性能优…...

uniapp中如何使用image图片
当在UniApp中使用图片时,可以通过<image>标签将图片显示在页面上。这个标签可以指定src属性来引用图片,并且可以通过mode属性来设置图片的显示模式。除此之外,还可以利用click事件来实现图片的点击事件。在编写代码时,要注意…...
docker-compose 安装gitlab
写在前面的话:docker-compose的文件是通用的,因此可以切换任意版本的gitlab的镜像版本。 往期docker-compose部署系列如: docker-compose语法格式docker-compose部署openldapdocker-compose 安装Sonar并集成gitlab 文章目录 1. 参考文档2. 环…...

到底是前端验证还是后端验证
背景 软件应用研发中, 前端验证还是后端验证这是意识与认知问题。鉴于某些入门同学还不清楚,我们再来看下: 一. 从软件行业来自国外 Q: 前端验证和后端验证都是对同一个数据的验证,有什么区别? A: 二者的目的不同&…...

AlignBench:量身打造的中文大语言模型对齐评测
对齐(Alignment),是指大语言模型(LLM)与人类意图的一致性。换言之,就是让LLM生成的结果更加符合人类的预期,包括遵循人类的指令,理解人类的意图,进而能产生有帮助的回答等…...

asp.net core 教程
asp.net core 教程 写在前面新建项目Get和PostGETPOST MVC-模型控制视图如何通俗理解MVCMVC架构---文件夹详解Connected ServicesPropertieswwwroot依赖项ControllersModelsViews 代码实例 API模型(前后端分离)前端代码后端代码 文件配置优先级优先级顺序…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...
React Native 导航系统实战(React Navigation)
导航系统实战(React Navigation) React Navigation 是 React Native 应用中最常用的导航库之一,它提供了多种导航模式,如堆栈导航(Stack Navigator)、标签导航(Tab Navigator)和抽屉…...

【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...

华为OD机试-食堂供餐-二分法
import java.util.Arrays; import java.util.Scanner;public class DemoTest3 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint a in.nextIn…...
土地利用/土地覆盖遥感解译与基于CLUE模型未来变化情景预测;从基础到高级,涵盖ArcGIS数据处理、ENVI遥感解译与CLUE模型情景模拟等
🔍 土地利用/土地覆盖数据是生态、环境和气象等诸多领域模型的关键输入参数。通过遥感影像解译技术,可以精准获取历史或当前任何一个区域的土地利用/土地覆盖情况。这些数据不仅能够用于评估区域生态环境的变化趋势,还能有效评价重大生态工程…...

免费PDF转图片工具
免费PDF转图片工具 一款简单易用的PDF转图片工具,可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件,也不需要在线上传文件,保护您的隐私。 工具截图 主要特点 🚀 快速转换:本地转换,无需等待上…...

代码规范和架构【立芯理论一】(2025.06.08)
1、代码规范的目标 代码简洁精炼、美观,可持续性好高效率高复用,可移植性好高内聚,低耦合没有冗余规范性,代码有规可循,可以看出自己当时的思考过程特殊排版,特殊语法,特殊指令,必须…...
C语言中提供的第三方库之哈希表实现
一. 简介 前面一篇文章简单学习了C语言中第三方库(uthash库)提供对哈希表的操作,文章如下: C语言中提供的第三方库uthash常用接口-CSDN博客 本文简单学习一下第三方库 uthash库对哈希表的操作。 二. uthash库哈希表操作示例 u…...
【学习笔记】erase 删除顺序迭代器后迭代器失效的解决方案
目录 使用 erase 返回值继续迭代使用索引进行遍历 我们知道类似 vector 的顺序迭代器被删除后,迭代器会失效,因为顺序迭代器在内存中是连续存储的,元素删除后,后续元素会前移。 但一些场景中,我们又需要在执行删除操作…...
tomcat入门
1 tomcat 是什么 apache开发的web服务器可以为java web程序提供运行环境tomcat是一款高效,稳定,易于使用的web服务器tomcathttp服务器Servlet服务器 2 tomcat 目录介绍 -bin #存放tomcat的脚本 -conf #存放tomcat的配置文件 ---catalina.policy #to…...