面试题:谈谈你对观察者和订阅发布的理解
面试题:谈谈你对观察者和订阅发布的理解
1. 观察者设计模式
-
场景引入之杂志订阅:小王想要购买一本尚未出版的杂志,他向出版社预订该杂志并提供联系方式,一旦该杂志出版,出版社就会根据小王预留的联系方式通知他可以来购买了。这个简单的场景就是一个简单的观察者模式,可以将小王扩充到任意数量的消费者,一旦某个杂志出版了,出版社就可以根据维护的订阅该杂志的消费者列表进行逐一通知。
-
观察者模式的解释:观察者模式定义了一种一对多的依赖关系,使得当一个对象(主题,被观察者,Subject)的状态改变时,所有依赖它的对象(观察者,Observer)都会得到通知并自动更新。观察者模式中,观察者和被观察者的关系是通过被观察者建立的。
- 被观察者身上必须有三个方法:添加观察者(addObserver)、删除观察者(delObserver)、通知观察者(notifyObserver)。被观察者维护一个观察者列表,使用其自身的
addObserver和delObserver方法添加或删除观察者。 - 观察者身上必须有一个方法:更新(update)。一旦被观察者的状态改变,就会调用
notifyObserver方法,遍历其维护的观察者列表,同时调用每个观察者的update的方法,用于对被观察者的状态改变做出响应。 - 将观察者模式与杂志订阅进行类比,被观察者是出版社(Subject),其状态是对应杂志是否出版,一旦杂志出版(状态变化),出版社就会根据其维护的订阅者列表逐一通知(调用
notifyObserver方法,该方法遍历每个观察者,调用对应观察者的update方法)。观察者收到通知后,就会来出版社购买杂志(update方法的调用结果)。同时,如果有其他消费者(Observer)想要订阅杂志,就要来出版社留下联系方式(出版社调用其addObserver方法)。
- 被观察者身上必须有三个方法:添加观察者(addObserver)、删除观察者(delObserver)、通知观察者(notifyObserver)。被观察者维护一个观察者列表,使用其自身的
-
观察者模式之杂志订阅的 TS 实现
-
观察者接口
/* 观察者接口:所有观察者必须实现 update 方法 */ interface Observer {update(magazineName: string): void; } -
被观察者接口
/* 主题/被观察者接口:被观察者必须实现 add、del、notifyAll 方法,并且必须维护一个观察者列表 */ interface Subject {add(observer: Observer): Subject;del(observer: Observer): Subject;notifyAll(msg: string): void; } -
观察者实现/消费者类的定义
/* 消费者类 —— 实现观察者接口 */ class Subscriber implements Observer {name: string;constructor(name: string) {this.name = name;}update(magazineName: string): void {console.log(`消费者 ${this.name} 收到了消息:${magazineName} 现在可以购买了!!!`);} } -
被观察者实现/出版社类的定义
/* 出版社类 —— 实现被观察者接口 */ class Publisher implements Subject {private observerList: Observer[];constructor() {/* 出版社维护的消费者列表 ==> 相当于被观察者维护的观察者列表 */this.observerList = [];}add(observer: Observer): Publisher {/* 添加观察者 */this.observerList.push(observer);return this;}del(observer: Observer): Publisher {/* 删除观察者 */const index = this.observerList.indexOf(observer);if (index !== -1) {this.observerList.splice(index, 1);}return this;}notifyAll(msg: string): void {/* 通知所有观察者 */this.observerList.forEach(observer => observer.update(msg));}publishMagazine(magazineName: string): void {/* 发布杂志 */this.notifyAll(magazineName);} } -
测试样例
/* 测试用例 */ // 创建出版社 const publisher: Publisher = new Publisher(); // 创建消费者 const chris: Subscriber = new Subscriber("Chris"); const jerry: Subscriber = new Subscriber("Jerry"); const tom: Subscriber = new Subscriber("Tom"); // 消费者订阅 publisher.add(chris).add(jerry).add(tom); // 出版社杂志出版(自动通知消费者) publisher.publishMagazine("《简爱》"); /* 输出:消费者 Chris 收到了消息:《简爱》 现在可以购买了!!!消费者 Jerry 收到了消息:《简爱》 现在可以购买了!!!消费者 Tom 收到了消息:《简爱》 现在可以购买了!!! */
-
2. 订阅发布消息范式
-
场景引入之仍是杂志订阅:假设有许多消费者想要订阅杂志,出版社需要维护一个庞大的消费者列表。当杂志出版时,出版社需要根据这个列表逐一通知所有订阅者来购买杂志。显然,随着消费者列表中的元素数量增加,出版社需要花费大量时间来维护列表、处理订阅和取消订阅请求,并逐一通知用户。这种做法显然会耽误出版社的本职工作——出版杂志。因此,出版社决定寻找一个代理中介,由这个中介来维护订阅者列表。这样一来,出版社除了出版杂志之外,只需要通知中介即可,中介则会根据维护的订阅者列表,逐一通知所有订阅者。这就是发布-订阅消息范式。通过使用发布-订阅消息范式,观察者与被观察者之间的耦合性得到了降低。被观察者只需要通知中介,这称为“发布”;观察者只需要向中介注册,这称为“订阅”。一旦中介收到被观察者的通知,就会将相应的信息告知所有观察者。
-
订阅发布消息范式的解释:订阅发布消息范式可以理解为观察者模式的升级版,在观察者和被观察者之间引入了一个中介。在这种范式中,我们引入了几个概念:消息、订阅者、发布者和发布订阅中心。发布者发布一个消息,包含消息名和消息内容;订阅者注册自己的信息,包含消息名和回调函数;发布订阅中心维护一个消息对象和一个回调对象,每个对象的元素为列表,列表名为消息名,列表中的元素为发布者发布的消息内容或订阅者注册的回调函数。发布者通过发布订阅中心发布消息,订阅者通过发布订阅中心注册信息,发布订阅中心根据消息类型,使用对应的消息内容和回调函数执行相应的逻辑。
- 发布订阅中心必须有三个方法:发布(publish)、订阅(subscribe)、通知(notify)。发布订阅中心维护两个对象:一个是以消息名为键,消息内容数组为值;另一个是以消息名为键,回调函数数组为值。
- 发布者和订阅者必须拥有发布订阅中心的引用,以便在合适的时机根据自身逻辑进行发布或订阅。
-
订阅发布消息范式之考虑一个新闻发布系统
发布者A发布关于“体育”的消息。发布者B也发布关于“体育”的消息。
发布者C发布关于“科技”的消息。
订阅者X订阅“体育”主题,所以它会收到发布者A和发布者B的所有“体育”消息。
订阅者Y订阅“科技”主题,它只会收到发布者C关于“科技”的消息。
优点:发布者和订阅者彼此独立,可以独立扩展和修改,消息代理负责消息的传递,简化了系统的复杂性。
-
订阅发布消息范式之杂志订阅 + 商品预购的 JS 实现
此示例想要表明:发布订阅中心根据消息管理发布消息信息和订阅回调,发布者可以有多个,不同发布者发布的消息可能被相同的订阅者订阅。一个消息即可以被不同发布者发布,也可以被不同消费者消费。
-
发布者/出版商 & 商家(两种消息类型,“books”, “products”)
/* 发布者 */ class Publisher {constructor(pubsub, msgType) {/* 每个发布者都可以通过自身访问到订阅发布中心 this.pubsub*/this.pubsub = pubsub;/* this.type 表示当前发布者发布的消息类型 */this.msgType = msgType;}publish(msg) {/* 发布类型为 this.msgType 的消息 */this.pubsub.publish(this.msgType, msg);} } -
订阅者/消费者
/* 订阅者 */ class Subscriber {constructor(pubsub, name) {/* 每一个订阅者都可以通过自身访问到订阅发布中心 this.pubsub */this.pubsub = pubsub;/* 消费者姓名 */this.name = name;}subscribe(msgType, cb) {/* 订阅类型为 msgType 的消息,发布订阅中心使用每个 cb 处理对应消息类型的所有消息内容 */this.pubsub.subscribe(msgType, cb);} } -
发布订阅中心
/* 发布订阅中心 */ class PubSub {constructor() {/* 以下两个对象的元素都是数组,数组名表示消息类型,数组中的元素表示一个消息内容 or 一个回调函数 */this.messages = {}; // 根据消息类型维护的消息内容对象this.listeners = {} // 根据消息类型维护的回调函数对象}publish(msgType, msg) {const isExist = this.messages[msgType];if (!isExist) {this.messages[msgType] = []}this.messages[msgType].push(msg)/* 一旦发布者发布,就通知订阅者,即执行订阅者传递的回调函数 */this.notifyAsType(msgType)}subscribe(msgType, cb) {const isExist = this.listeners[msgType];if (!isExist) {this.listeners[msgType] = []}this.listeners[msgType].push(cb)}notifyAsType(msgType) {const messages = this.messages[msgType];const listeners = this.listeners[msgType];if (!listeners) return;/* 所谓的通知订阅者,即调用订阅者传递的回调,同时接收一个参数,为当前消息类型的消息信息构成的数组 */listeners.forEach((cb) => {cb(messages);})} } -
测试样例
/* 测试样例 */ /* 订阅发布中心 */ const broker = new PubSub(); // 发布订阅中心 /* 发布者 */ const publisher = new Publisher(broker, "books"); // 发布者之出版社 const seller = new Publisher(broker, "products"); // 发布者之商家 /* 订阅者 */ const chris = new Subscriber(broker, "chris"); const jerry = new Subscriber(broker, "jerry"); /* 订阅消息 */ chris.subscribe("books", (message, name = "chris") => {/* 定义简单的处理消息内容的回调函数 */console.log(`${name} 收到通知,出版社出新书了,现在出版书籍为 ${message}`); }); jerry.subscribe("books", (message, name = "jerry") => {console.log(`${name} 收到通知,出版社出新书了,现在出版书籍为 ${message}`); }); jerry.subscribe("products", (message, name = "jerry") => {console.log(`${name} 收到通知,新品上架了,现在有新品为 ${message}`); }) /* 发布消息 */ publisher.publish("简爱"); publisher.publish("悉达多"); publisher.publish("风沙星辰"); seller.publish("水杯"); seller.publish("牙刷"); seller.publish("牙膏"); /* chris 收到通知,出版社出新书了,现在出版书籍为 简爱jerry 收到通知,出版社出新书了,现在出版书籍为 简爱chris 收到通知,出版社出新书了,现在出版书籍为 简爱,悉达多jerry 收到通知,出版社出新书了,现在出版书籍为 简爱,悉达多chris 收到通知,出版社出新书了,现在出版书籍为 简爱,悉达多,风沙星辰jerry 收到通知,出版社出新书了,现在出版书籍为 简爱,悉达多,风沙星辰jerry 收到通知,新品上架了,现在有新品为 水杯jerry 收到通知,新品上架了,现在有新品为 水杯,牙刷jerry 收到通知,新品上架了,现在有新品为 水杯,牙刷,牙膏 */
-
3. 辨·区别
| 区别 | 观察者 | 订阅发布 |
|---|---|---|
| 类型 | 设计模式 | 消息范式 |
| 对象数量 | 至少两个 (观察者>=1,被观察者>=1) | 至少三个 (发布者>=1,订阅者>=1,发布订阅中心=1) |
| 关注重点 | 被观察者 (方法:添加观察者、移除观察者、通知观察者) | 发布订阅中心 (方法:发布、订阅、通知) |
| 耦合程度 | 松耦合 (观察者功能不存粹,需要将自身的变化响应式的反馈到观察者) | 解耦合 (发布者只关注发布逻辑,订阅者只关注订阅逻辑和实现接收到通知后的逻辑) |
| 图像对比 | ![]() | ![]() |
REFERENCES
https://juejin.cn/post/6978728619782701087
https://refactoringguru.cn/design-patterns/observer
相关文章:
面试题:谈谈你对观察者和订阅发布的理解
面试题:谈谈你对观察者和订阅发布的理解 1. 观察者设计模式 场景引入之杂志订阅:小王想要购买一本尚未出版的杂志,他向出版社预订该杂志并提供联系方式,一旦该杂志出版,出版社就会根据小王预留的联系方式通知他可以来…...
下载文件流
export function downloadFile(file, name, type) { const link document.createElement(‘a’) link.href window.URL.createObjectURL(new Blob([file], { type: type })) link.target ‘_blank’ link.download name document.body.appendChild(link) link.click() docu…...
有开源软件,也有开源硬件?
开源软件或库有很多,例如 Linux 操作系统的内核 The Linux Kernel Archiveshttps://www.kernel.org/ 开源的各种Linux发行版本,Ubuntu 、CentOS等 Enterprise Open Source and Linux | Ubuntuhttps://ubuntu.com/ 开源的视觉函数库,OpenC…...
【TensorFlow深度学习】卷积层变种与深度残差网络原理
卷积层变种与深度残差网络原理 卷积层变种与深度残差网络:探究卷积神经网络的进化与优化策略卷积层:深度学习的基石变种与卷积层变种深差网络:深度网络的优化策略实战代码示例:ResNet模块实现结语 卷积层变种与深度残差网络&#…...
每日一题《leetcode-- LCR 025.两数相加||》
https://leetcode.cn/problems/lMSNwu/ 分别把给定的两个链表翻转,然后从头开始相加。 /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/ //反转链表 struct ListNode* reverselist(struct ListNode*h…...
MySQL数据库的约束
MySQL对于数据库存储的数据, 做出一些限制性要求, 就叫做数据库的"约束". 在每一列的 列名, 类型 后面加上"约束". 一. not null (非空) 指定某列不能存储null值. 二. unique (唯一) 保证这一列的每行必须有唯一值. 我们可以看到, 给 table 的 sn 列插…...
计算机毕业设计 | springboot+vue会议室管理系统(附源码)
1,绪论 1.1 项目背景 随着企业规模的不断扩大,会议室管理愈加复杂。传统的手工预约会议室的方式已经无法满足现代企业的需求,因此,开发一套会议室系统方案变得尤为重要。会议室系统可以实现会议室的在线预约、会议室资源的有效利…...
常见端口及其脆弱点
端口及脆弱性 ⚫ FTP (21/TCP) 1.默认用户名密码anonymous:anonymous 2.暴力破解密码 3.VSFTP 某版本后门 ⚫ SSH (22/TCP) 1.部分版本 SSH 存在漏洞可枚举用户名 2.暴力破解密码 ⚫ Telent (23/TCP) 1.暴力破解密码 2.嗅探抓取明文密码 ⚫ SMTP (25/TCP) 1.无认证…...
JS函数的进阶
目录 递归和堆栈Rest参数与Spread语法闭包全局对象高阶函数函数对象和绑定装饰者模式和转发深入理解箭头函数递归和堆栈 递归 递归是一种编程技巧,函数在其定义中直接或间接地调用自身,通常用来解决具有明确递归结构的问题,如树形结构遍历、排序算法(如快速排序)、数学问…...
【UE+GIS】UE5GIS CAD或shp构建3D地形
贴合地形的矢量图形实现方法 一、灰度图的制作和拉伸换算1、基于高程点集实现2、基于等高线实现3、拉伸计算 二、生成地形模型的实现方案1、3Dmax导入灰度图2、使用ArcMap/Arcpro/FME等GIS数据处理工具3、UE导入灰度图 三、地形上叠加地形渲染效果的实现方案1、贴花2、数据渲染…...
Unity学习笔记---音视频播放
音频 Audiolistener组件 AudioListener组件是音频监听器,将组件挂在角色或camera上面,每个场景中最多只有一个AudioListener组件。 AudioSource组件 AudioSource组件是音源,用来播放音频AudioClip.将他挂在产生声音的物体上,可…...
项目集成过程中的makefile记录
项目集成过程中的makefile记录 文章目录 项目集成过程中的makefile记录1.基础概念注释打印赋值方式常用变量$ 伪目标函数wildcard 多目录、文件操作 2.思路梳理**需求分析**目录结构 3.可行示例 持续更新中1.基础概念 注释 # 示例: # 项目名称打印 echo "H…...
Vue3 -Computed计算属性
前言: Computed属性属于Vue3中的响应式核心(与之共同说明的还有ref,reactive,watch...) 接受一个 getter 函数,返回一个只读的响应式 ref 对象。该 ref 通过 .value 暴露 getter 函数的返回值。它也可以接受一个带有 get 和 set…...
MySQL—函数—日期函数(基础)
一、引言 接下来讨论和学习关于函数的第三个方面——日期函数。 常见的MySQL当中的日期函数。 注意: 1、CURDATE():cur:current 当前的,返回的是当前日期。 2、CURTIME():当前时间。 3、NOW:当前的日期和…...
Java+SVNCloud+Mysql课程设计
文章目录 1、主要内容2、所需准备3、与sql访问的中间类:SqlMessage4、窗口界面5、main方法 1、主要内容 课程设计,主要通过Javas wing创建窗口,jdbc连接云端mysql数据库进行基本操作,支持随机生成数据并用动态展示数据结果。 先…...
MySQL之创建高性能的索引(四)
创建高性能的索引 空间数据索引(R-Tree) MyISAM表支持空间索引,可以用作地理数据存储。和B-Tree索引不同,这类索引无须前缀查询。空间索引会从所有维度来索引数据。查询时,可以有效地使用任意维度来组合查询。必须使用MySQL的GIS相关函数如…...
Python 限制输入数的范围
Python 限制输入数的范围 在 Python 编程中,我们经常需要限制用户输入的数据范围,以避免一些可能出现的问题。例如,在一个游戏程序中,我们可能想要确保玩家的分数在某个范围内,而不是太高或太低。在这个博文中&#x…...
STM32两轮平衡小车原理详解
STM32两轮平衡小车是一种基于STM32微控制器的智能机器人,它能够通过传感器和算法实现自我平衡。以下是对STM32两轮平衡小车原理的详解,以及一些基础的代码示例。 原理详解 1. 系统组成 主控制器:STM32系列微控制器,作为小车的大…...
(笔记)如何评价一个数仓的好坏
如何评价一个数仓的好坏 1数据质量产生原因评估方法流程 2模型建设产生问题原因评估方法流程 3数据安全产生问题原因评估方法流程 4成本/性能产生问题原因评估方法流程 5 用户用数体验产生问题原因评估方法流程 6数据资产覆盖产生问题原因评估方法流程 数仓评价好坏是对数仓全流…...
友善RK3399v2平台利用rkmpp实现硬件编解码加速
测试VPU 编译mpp sudo apt update sudo apt install gcc g cmake make cd ~ git clone https://github.com/rockchip-linux/mpp.git cd mpp/build/linux/aarch64/ sed -i s/aarch64-linux-gnu-gcc/gcc/g ./arm.linux.cross.cmake sed -i s/aarch64-linux-gnu-g/g/g ./arm.lin…...
蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练
前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1):从基础到实战的深度解析-CSDN博客,但实际面试中,企业更关注候选人对复杂场景的应对能力(如多设备并发扫描、低功耗与高发现率的平衡)和前沿技术的…...
渲染学进阶内容——模型
最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...
MVC 数据库
MVC 数据库 引言 在软件开发领域,Model-View-Controller(MVC)是一种流行的软件架构模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种模式有助于提高代码的可维护性和可扩展性。本文将深入探讨MVC架构与数据库之间的关系,以…...
《通信之道——从微积分到 5G》读书总结
第1章 绪 论 1.1 这是一本什么样的书 通信技术,说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号(调制) 把信息从信号中抽取出来&am…...
Mac软件卸载指南,简单易懂!
刚和Adobe分手,它却总在Library里给你写"回忆录"?卸载的Final Cut Pro像电子幽灵般阴魂不散?总是会有残留文件,别慌!这份Mac软件卸载指南,将用最硬核的方式教你"数字分手术"࿰…...
HBuilderX安装(uni-app和小程序开发)
下载HBuilderX 访问官方网站:https://www.dcloud.io/hbuilderx.html 根据您的操作系统选择合适版本: Windows版(推荐下载标准版) Windows系统安装步骤 运行安装程序: 双击下载的.exe安装文件 如果出现安全提示&…...
【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...
涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战
“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...
QT3D学习笔记——圆台、圆锥
类名作用Qt3DWindow3D渲染窗口容器QEntity场景中的实体(对象或容器)QCamera控制观察视角QPointLight点光源QConeMesh圆锥几何网格QTransform控制实体的位置/旋转/缩放QPhongMaterialPhong光照材质(定义颜色、反光等)QFirstPersonC…...
使用LangGraph和LangSmith构建多智能体人工智能系统
现在,通过组合几个较小的子智能体来创建一个强大的人工智能智能体正成为一种趋势。但这也带来了一些挑战,比如减少幻觉、管理对话流程、在测试期间留意智能体的工作方式、允许人工介入以及评估其性能。你需要进行大量的反复试验。 在这篇博客〔原作者&a…...

