面试题:谈谈你对观察者和订阅发布的理解

面试题:谈谈你对观察者和订阅发布的理解

1. 观察者设计模式

  • 场景引入之杂志订阅:小王想要购买一本尚未出版的杂志,他向出版社预订该杂志并提供联系方式,一旦该杂志出版,出版社就会根据小王预留的联系方式通知他可以来购买了。这个简单的场景就是一个简单的观察者模式,可以将小王扩充到任意数量的消费者,一旦某个杂志出版了,出版社就可以根据维护的订阅该杂志的消费者列表进行逐一通知。

  • 观察者模式的解释:观察者模式定义了一种一对多的依赖关系,使得当一个对象(主题,被观察者,Subject)状态改变时,所有依赖它的对象(观察者,Observer)都会得到通知并自动更新。观察者模式中,观察者和被观察者的关系是通过被观察者建立的。

    • 被观察者身上必须有三个方法:添加观察者(addObserver)、删除观察者(delObserver)、通知观察者(notifyObserver)。被观察者维护一个观察者列表,使用其自身的 addObserverdelObserver 方法添加或删除观察者。
    • 观察者身上必须有一个方法:更新(update)。一旦被观察者的状态改变,就会调用 notifyObserver 方法,遍历其维护的观察者列表,同时调用每个观察者的 update 的方法,用于对被观察者的状态改变做出响应。
    • 将观察者模式与杂志订阅进行类比,被观察者是出版社(Subject),其状态是对应杂志是否出版,一旦杂志出版(状态变化),出版社就会根据其维护的订阅者列表逐一通知(调用 notifyObserver 方法,该方法遍历每个观察者,调用对应观察者的 update 方法)。观察者收到通知后,就会来出版社购买杂志(update 方法的调用结果)。同时,如果有其他消费者(Observer)想要订阅杂志,就要来出版社留下联系方式(出版社调用其 addObserver 方法)。
    img img
  • 观察者模式之杂志订阅的 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)。发布订阅中心维护两个对象:一个是以消息名为键,消息内容数组为值;另一个是以消息名为键,回调函数数组为值。
    • 发布者和订阅者必须拥有发布订阅中心的引用,以便在合适的时机根据自身逻辑进行发布或订阅。
    img
  • 订阅发布消息范式之考虑一个新闻发布系统

    发布者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)
关注重点被观察者
(方法:添加观察者、移除观察者、通知观察者)
发布订阅中心
(方法:发布、订阅、通知)
耦合程度松耦合
(观察者功能不存粹,需要将自身的变化响应式的反馈到观察者)
解耦合
(发布者只关注发布逻辑,订阅者只关注订阅逻辑和实现接收到通知后的逻辑)
图像对比观察者.jpg发布订阅模拟观察者模式.jpg

REFERENCES

https://juejin.cn/post/6978728619782701087

https://refactoringguru.cn/design-patterns/observer

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

YOLOv8_obb预测流程-原理解析[旋转目标检测理论篇]

YOLOv8_obb的预测流程,主要分预处理模块、推理模块和后处理模块。这里面有很多内容是和目标检测预测流程是重合的,主要区别在于Angle分支、NMS后处理以及regularize_rboxes部分。本文也主要介绍一下这三个模块,其他模块可以结合YOLOv8预测流程-原理解析[目标检测理论篇]一起…

最新版wordpress网创资源美化以及更新自动同步插件

最新更新了美化右侧悬浮图标 底部分类板块,以及文章自动同步插件 1.支持分类替换 将主站同步过来的文章分类进行替换 2.支持本地化文章图片 (使用储存桶可能会导致无法保存图片) 3.支持自定义文章作者(选择多个作者则同步到的…

辩证 逻辑学 | 洞察 事物矛盾及变化规律 在形式逻辑基础上 学会辩证思维(40节课)

课程下载:辩证逻辑学洞察事物矛盾及变化规律在形式逻辑基础上学会辩证思维(40节课)-课程网盘链接提取码下载.txt资源-CSDN文库 更多资源下载:关注我。 在形式逻辑的基础上,学会 辩证思维 敏锐 洞察事物发展变化的规…

RPG Maker MV角色战斗动画记录

角色战斗动画记录 角色战斗状态判断的语句赋值 战斗管理战斗精灵创建精灵进行角色的更新 角色战斗状态 角色的战斗状态是由 Game_Battler 类中的 _actionState 属性的字符串决定的,它有哪些值呢? undecided 未确定或者说是操作状态inputting 输入waiti…

开源VS闭源:大模型之争,究竟谁更胜一筹?

随着人工智能技术的快速发展,大模型作为其中的核心组件,已经引起了业界的广泛关注。在大模型的研发过程中,开源与闭源成为了两个备受争议的话题。究竟开源与闭源谁更好?本文将从多个角度进行深入分析,为大家揭示真相。…

React + SpringBoot开发用户中心管理系统

用户中心项目搭建笔记 技术栈 前端技术栈 “react”: “^18.2.0”,ant-design-pro 后端技术栈 SpringBoot 2.6.x 项目源码地址 https://gitee.com/szxio/user-center 前端项目搭建 快速搭建一个后端管理系统项目框架 初始化 antDesignPro 官网: https://…

godot.bk:how to add map to the game

1.项目构建如下,map是我们点击start之后才渲染出来的 mian.tscn --main.gd --background(textureact) --start(button) --button.gd sourceFile map.tscn --tilemap --tileset 2.main.gd:注意main.gd并不定义信号,它只是接收信号而已 extend…

新书推荐:1.2 动态链接库与API

本节必须掌握的知识点: kernel32.dll user32.dll gdi32.dll ■动态链接库 最早的软件开发过程,所有的功能实现都是有程序员独立完成的。在这个过程中,我们很快就会发现,有很多常用的功能模块是可以重复利用的,我们将…

UE5.1_常用快捷键

UE5.1_常用快捷键 shift1,,模式选择 shift2,,模式选择 shift3,,模式选择 shift4,,模式选择 shift5,,模式选择 shift6,,模式选择 …

定义多层Toggle选项,第一层为总开关

本文为笔记,暂未整理。主要逻辑为,我们有需求,需要再第一个Toggle选中之后,余下的几个Toggle才是可以被修改的状态。 ①:当第一个是灰色的时候,余下两个Toggle都是灰色的,并且都是不可选中的。…

【全开源】Java共享台球室无人系统支持微信小程序+微信公众号+H5

智能引领台球新体验 一、引言:共享经济的新篇章 在共享经济的大潮中,各类共享服务层出不穷,为人们的生活带来了极大的便利。共享台球室作为其中的一员,以其独特的魅力吸引了众多台球爱好者的目光。而今天,我们要介绍…

代码随想录算法训练营第三十六 | ● 435. 无重叠区间 ● 763.划分字母区间 ● 56. 合并区间

今天的三道题都是区间问题 435. 无重叠区间 讲解链接&#xff1a;https://programmercarl.com/0435.%E6%97%A0%E9%87%8D%E5%8F%A0%E5%8C%BA%E9%97%B4.html 思路分析在代码注释 class Solution { public:static bool cmp(const vector<int>&a, const vector<int&…

AI智能体|基于已有的扣子Coze Bot进行插件化改造

大家好&#xff0c;我是无界生长&#xff0c;国内最大AI付费社群“AI破局俱乐部”初创合伙人。 AI智能体&#xff5c;基于已有的扣子Coze Bot进行插件化改造本文通过案例演示的方式&#xff0c;介绍了如何将扣子Coze平台创建的Bot改造成插件并发布到插件商店供第三方使用。如果…

Python的文件管理

读取文件 首先我们可以先创建一个工程项目&#xff0c;如图所示&#xff1a; 打开我们名为1.读取文件.py的python文件&#xff0c;然后我们可以写下读取Python文件的代码&#xff0c;代码如下&#xff1a; f open("1.txt", "r") print(f.read()) f.clos…

burp插件new_xp_capcha识别验证码的简易安装

1.new_xp_capcha 插件是大佬开发的可以正常白嫖&#xff0c;感谢大佬&#xff0c;我找了个不需要任何高级操作就可以做的安装手法&#xff0c;因为我在网上搜了一下就发现这个的安装过程攻略都还蛮复杂&#xff0c;我这里用了个简单的手法 2.安装 下载地址&#xff1a;smxia…

[C][可变参数列表]详细讲解

目录 1.宏含义及使用2.宏原理分析1.原理2.宏理解 1.宏含义及使用 依赖库stdarg.hva_list 其实就是char*类型&#xff0c;方便后续按照字节进行指针移动 va_start(arg, num) 使arg指向可变参数部分(num后面) va_arg(arg, int) 先让arg指向下个元素&#xff0c;然后使用相对位置…

Wireshark 如何查找包含特定数据的数据帧

1、查找包含特定 string 的数据帧 使用如下指令&#xff1a; 双引号中所要查找的字符串 frame contains "xxx" 查找字符串 “heartbeat” 示例&#xff1a; 2、查找包含特定16进制的数据帧 使用如下指令&#xff1a; TCP&#xff1a;在TCP流中查找 tcp contai…

MySQL中:cmd下输入命令mysql -uroot -p 连接数据库错误

目录 问题cmd下输入命令mysql -uroot -p错误 待续、更新中 问题 cmd下输入命令mysql -uroot -p错误 解决 配置环境变量&#xff1a;高级系统设置——环境变量——系统变量——path编辑——新建——MySQL.exe文件路径&#xff08;如下图所示&#xff09; phpstudy2018软件下&am…

C语言 | Leetcode C语言题解之第127题单词接龙

题目&#xff1a; 题解&#xff1a; struct Trie {int ch[27];int val; } trie[50001];int size, nodeNum;void insert(char* s, int num) {int sSize strlen(s), add 0;for (int i 0; i < sSize; i) {int x s[i] - ;if (trie[add].ch[x] 0) {trie[add].ch[x] size;m…

Web安全:软件开发的安全问题与解决方案

「作者简介」&#xff1a;2022年北京冬奥会网络安全中国代表队&#xff0c;CSDN Top100&#xff0c;就职奇安信多年&#xff0c;以实战工作为基础对安全知识体系进行总结与归纳&#xff0c;著作适用于快速入门的 《网络安全自学教程》&#xff0c;内容涵盖系统安全、信息收集等…