观察者模式与发布订阅模式

观察者模式

定义:

观察者模式是一种行为型设计模式,定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新

结构图:

https://zh.wikipedia.org/zh-cn/%E8%A7%82%E5%AF%9F%E8%80%85%E6%A8%A1%E5%BC%8F

ES6简易代码实现: 

//ts环境下的es6类模拟,已去掉ts下的类型检查和定义
/**
 * 观察者(Observer):观察者是接收主题通知的对象。观察者需要实现一个更新方法,当收到主题的通知时,调用该方法进行更新操作。
 */
class Observe {
  constructor(name) {
    this.name = name;
  }
  update(payload) {
    console.log(`${this.name}观察的数据发生了变化:${payload}`);
  }
}
class Subject {
  constructor() {
    this.observeList = [];
  }
  addObserve(observe) {
    this.observeList.push(observe);
  }
  removeObserve(observe) {
    this.observeList = this.observeList.filter((item) => item != observe);
  }
  notify(payload) {
    this.observeList.forEach((item) => item.update(payload));
  }
}

ES简易代码实现: 

var Observe = /** @class */ (function () {
    function Observe(name) {
        this.name = name;
    }
    Observe.prototype.update = function (payload) {
        console.log("".concat(this.name, "\u89C2\u5BDF\u7684\u6570\u636E\u53D1\u751F\u4E86\u53D8\u5316\uFF1A").concat(payload));
    };
    return Observe;
}());
var Subject = /** @class */ (function () {
    function Subject() {
        this.observeList = [];
    }
    Subject.prototype.addObserve = function (observe) {
        this.observeList.push(observe);
    };
    Subject.prototype.removeObserve = function (observe) {
        this.observeList = this.observeList.filter(function (item) { return item != observe; });
    };
    Subject.prototype.notify = function (payload) {
        this.observeList.forEach(function (item) { return item.update(payload); });
    };
    return Subject;
}());
var subject = new Subject();
var observe = new Observe("xx");
var observe1 = new Observe("yy");
subject.addObserve(observe);
subject.addObserve(observe1);
subject.notify("zzz");

输出:

观察者模式通过将主题和观察者解耦,实现了对象之间的松耦合。当主题的状态发生改变时,所有依赖于它的观察者都会收到通知并进行相应的更新。 

发布订阅模式

定义:

发布-订阅模式其实是一种对象间一对多的依赖关系,当一个对象的状态发送改变时,所有依赖于它的对象都将得到状态改变的通知。与观察者模式的定义相似,其最大的特点是:发布方与订阅方互不相知,双方都发送自己的消息内容到第三方,由第三方来处理。

订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Event Channel),当发布者(Publisher)发布该事件(Publish Event)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。

结构图:

观察者模式与发布订阅模式差异

  • 在观察者模式中,观察者是知道 Subject 的,Subject 一直保持对观察者进行记录。然而,在发布订阅模式中,发布者和订阅者不知道对方的存在。它们只有通过消息代理进行通信。

  • 在发布订阅模式中,组件是松散耦合的,正好和观察者模式相反。

  • 观察者模式大多数时候是同步的,比如当事件触发,Subject 就会去调用观察者的方法。而发布-订阅模式大多数时候是异步的(使用消息队列)。

  • 观察者模式需要在单个应用程序地址空间中实现,而发布-订阅更像交叉应用模式。

图示:

ES6简易代码实现

//ts环境下的es6类模拟,已去掉ts下的类型检查和定义
class EventEmitter {
  constructor() {
    //事件列表对象
    this.eventList = {};
  }
  //订阅
  on(eventName, fn) {
    //如果对象eventName不存在,先创建一个空数组
    if (!this.eventList[eventName]) {
      this.eventList[eventName] = [];
    }
    //如果事件存在,把fn添加到对应eventList[eventName]列表里面
    this.eventList[eventName].push(fn);
  }
  //取消订阅
  off(eventName, fn) {
    let callbacks = this.eventList[eventName];
    if (!callbacks) return false;
    if (!fn) return callbacks && (callbacks.length = 0);
    //遍历this.eventList[eventName]事件
    for (let i = 0; i < callbacks.length; i++) {
      console.log(callbacks[i].fn, "off------>callbacks[i].fn");
      console.log(callbacks[i], "off-callbacks[i]");
      //判断那个事件==fn,相同则删除
      if (callbacks[i] == fn || callbacks[i].fn == fn) {
        callbacks.splice(i, 1);
        break;
      }
    }
  }

  //监听一次
  once(eventName, fn) {
    // this箭头函数表达式是正则函数表达式的语法紧凑替代方案
    // 它没有自己的this、arguments、super或new.target关键字绑定。
    let on = (...args) => {
      this.off(eventName, on);
      //args代替arguments
      fn.apply(this, args);
    };
    //存储fn,确保单独使用off删除传入的fn时可以被删除掉
    on.fn = fn;
    console.log(on.fn, "once--->on.fn");
    console.log(on, "once-on");
    this.on(eventName, on);
  }
  //发布
  emit(eventName, data) {
    const callbacks = this.eventList[eventName];
    if (!callbacks) return;
    callbacks.forEach((element) => {
      element(data);
    });
  }
}

let events = new EventEmitter();
function test(data) {
  console.log("test is on:" + data);
}
function test1(data) {
  console.log("test1:" + data);
}
function test2(data) {
  console.log("test2:" + data);
}

源码once方法里面的on.fn =fn的作用

once订阅注册的订阅事件名均为on

考虑以下场景:还未发布时订阅者需要取消订阅某个once事件,由于once注册事件函数名均为on,off中cb是无法判断的,因此需要添加on.fn用来标识区分,所以才有off中判断条件为cb||cb.fn

上面的解释不仅说明了once方法里面 on.fn =fn的作用也说明了off方法里面for循环中if判断的后半句callbacks[i].fn == fn。

下面从代码打印中,我们来看没有经过发布,订阅者就取消订阅的once事件

输出语句如下:

events.once("example", test2);
events.off("example", test2);

输出内容:

ES5简易代码实现

//tsc编译js文件
var EventEmitter = /** @class */ (function () {
  function EventEmitter() {
    //事件列表对象
    this.eventList = {};
  }
  //订阅
  EventEmitter.prototype.on = function (eventName, fn) {
    //如果对象eventName不存在,先创建一个空数组
    if (!this.eventList[eventName]) {
      this.eventList[eventName] = [];
    }
    //如果事件存在,把fn添加到对应eventList[eventName]列表里面
    this.eventList[eventName].push(fn);
  };
  //取消订阅
  EventEmitter.prototype.off = function (eventName, fn) {
    var callbacks = this.eventList[eventName];
    if (!callbacks) return false;
    if (!fn) return callbacks && (callbacks.length = 0);
    //遍历this.eventList[eventName]事件
    for (var i = 0; i < callbacks.length; i++) {
      //判断那个事件==fn,相同则删除
      if (callbacks[i] == fn || callbacks[i].fn == fn) {
        callbacks.splice(i, 1);
        break;
      }
    }
  };
  //监听一次
  EventEmitter.prototype.once = function (eventName, fn) {
    var _this = this;
    var on = function () {
      var args = [];
      for (var _i = 0; _i < arguments.length; _i++) {
        args[_i] = arguments[_i];
      }
      _this.off(eventName, on);
      //args代替arguments
      fn.apply(_this, args);
    };
    //存储fn,确保单独使用off删除传入的fn时可以被删除掉
    on.fn = fn;
    this.on(eventName, on);
  };
  // 发布
  EventEmitter.prototype.emit = function (eventName, data) {
    var callbacks = this.eventList[eventName];
    if (!callbacks) return;
    callbacks.forEach(function (element) {
      element(data);
    });
  };
  return EventEmitter;
})();
var events = new EventEmitter();
function test(data) {
  console.log("test is on:" + data);
}
function test1(data) {
  console.log("test1:" + data);
}
function test2(data) {
  console.log("test2:" + data);
}
//1
events.on("example", test);
events.emit("example", "dddddd");
//2
events.once("example", test1);
events.emit("example", "true");
//3
events.once("example", test2);
events.emit("example", 123);
//events.off("example", test2);

输出:

参考

JavaScript 发布-订阅模式 - 掘金

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

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

相关文章

Uncertainty-Aware Mean Teacher(UA-MT)

Uncertainty-Aware Mean Teacher 0 FQA:1 UA-MT1.1 Introduction:1.2 semi-supervised segmentation1.3 Uncertainty-Aware Mean Teacher Framework 参考&#xff1a; 0 FQA: Q1: 不确定感知是什么意思&#xff1f;不确定信息是啥&#xff1f;Q2&#xff1a;这篇文章的精妙的点…

300分钟吃透分布式缓存-14讲:大数据时代,MC如何应对新的常见问题?

大数据时代 Memcached 经典问题 随着互联网的快速发展和普及&#xff0c;人类进入了大数据时代。在大数据时代&#xff0c;移动设备全面融入了人们的工作和生活&#xff0c;各种数据以前所未有的 速度被生产、挖掘和消费。移动互联网系统也不断演进和发展&#xff0c;存储、计…

掌握“这招”,平趴也能轻松捕获威胁情报!——利用流行度排名升级威胁情报收集

引言 威胁情报是提供强大网络安全服务的重要基石&#xff0c;这些服务可以保护各地的移动设备和互联网用户。但当今的互联网威胁是复杂且具有强适应性的&#xff0c;它们通过不断改变其面貌以逃避安全防御。这使得提供涵盖各种威胁形势的威胁情报变得日益困难&#xff0c;组织…

工具篇-- 定时任务xxl-job

文章目录 前言一、xxl-job 运行&#xff1a;1.1 下载并且启动&#xff1a;1.2 项目介绍&#xff1a;1.2.1 xxl-job-admin&#xff1a;1.2.1.1 xxl-job-admin 作用&#xff1a;1.2.1.2 xxl-job-admin 的配置&#xff1a; 1.2.2 xxl-job-executor-samples&#xff1a;1.2.2.1 pom…

51.仿简道云公式函数实战-文本函数-JOIN

1. JOIN函数 JOIN 函数可通过连接符将数组的值连成文本。 2. 函数用法 JOIN(数组,"连接符") 3. 函数示例 如需将复选框中勾选的选项通过”-“组合在一起&#xff0c;则可设置公式为JOIN(复选框组,"-") 4. 代码实战 首先我们在function包下创建text包…

基于springboot的新闻资讯系统的设计与实现

**&#x1f345;点赞收藏关注 → 私信领取本源代码、数据库&#x1f345; 本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目希望你能有所收获&#xff0c;少走一些弯路。&#x1f345;关注我不迷路&#x1f345;**一 、设计说明 1.1 课题背景…

Qt QWidget 简约美观的加载动画 第四季

&#x1f60a; 第四季来啦 &#x1f60a; 效果如下: 只有三个文件,可以直接编译运行的 //main.cpp #include "LoadingAnimWidget.h" #include <QApplication> #include <QVBoxLayout> #include <QGridLayout> int main(int argc, char *argv[]) …

亚洲唯一!京东荣获2024年度Gartner供应链技术创新奖背后的创新探索

序言&#xff1a; 序言&#xff1a;2月14日晚间&#xff0c;Gartner公布了2024年度Garter Power of the Profession供应链大奖&#xff0c;京东集团荣获供应链技术创新奖&#xff0c;成为获得该奖项的唯一亚洲企业。Gartner Power of the Profession供应链奖项已经举办十年&am…

驻场人员严重划水,愈演愈烈,要请领导出面吗?

你有没有遇到过团队成员偷懒的情况&#xff1f;比如你们一起完成某个项目目标&#xff0c;干着干着你发现&#xff0c;就只有你和几个核心人员比较上心&#xff0c;很多人都在划水。 你可能会觉得这是因为大家工作态度不好&#xff0c;甚至怀疑他们的人品&#xff0c;忍不住想…

MoonBit支持云原生调试功能

MoonBit 更新 1. 支持云原生调试功能 现在&#xff0c;你可以通过访问try.moonbitlang.cn&#xff0c;直接在浏览器中使用 devtools 调试 MoonBit 程序&#xff0c;无需安装任何软件。具体的使用步骤如下&#xff1a; 2. MoonBit 支持使用 for 关键字定义的函数式循环控制流 …

ShardingJDBC分库分表

目录 ShardingSphere ShardingJDBC客户端分库分表 ShardingProxy服务端分库分表 两者对比 ShardingJDBC分库分表实战 需求 步骤 分片策略汇总 ShardingSphere ShardingSphere最为核心的产品有两个&#xff1a;一个是ShardingJDBC&#xff0c;这是一个进行客户端分库分表…

Linux命令行常用命令

初识shell shell是系统的用户界面&#xff0c;提供了用户与内核进行交互操作的一种接口。它接收用户输入的命令并把它送入内核去执行。实际上shell是一个命令解释器&#xff0c;它解释用户输入的命令并且把用户的意图传达给内核。&#xff08;可以理解为用户与内核之间的翻译官…

npm/nodejs安装、切换源

前言 发现自己电脑上没有npm也没有node很震惊&#xff0c;难道我没写过代码么&#xff1f;不扯了&#xff0c;进入正题哈哈…… 安装 一般没有npm的话会报错&#xff1a; 无法将“npm”项识别为 cmdlet、函数、脚本文件或可运行程序的名称而且报这个错&#xff0c;我们执行…

Order By Limit不稳定性

文章目录 前置解决不确定性场景1 Order By索引1.1 背景1.2 不确定性产生原因1.2.1 正常情况下1.2.2 但是 1.3 补充1.4 场景1总结 场景2 Order by id2.1 背景2.2 不会产生不确定性原因1原因2 2.3 推荐使用方式 场景3 filesort3.1 背景3.2 不确定性产生原因3.3 内存排序和磁盘临时…

内衣洗衣机哪个牌子好用?甄选安利四款优质好用的内衣洗衣机

内衣洗衣机是近几年新兴的一种家用电器产品&#xff0c;正日益引起人们的重视。但是&#xff0c;面对市面上品牌繁多、款式繁多的内衣洗衣机&#xff0c;使得很多人都不知道该如何选择。身为一个数码家电博主&#xff0c;我知道这类产品在挑选方面有着比较深入的了解。为此&…

数据结构2月25日

第一道&#xff1a; 第二道&#xff1a; 1、插入到prev和next中间 1.new(struct list_head*)malloc(sizeof(struct list_head*)); if(newNULL) { printf("失败\n"); return; } new->nextprev->next; prev->nextnew; return; 2、删除prve和next…

redis——客户端

Redis是一个典型一对多服务器程序&#xff0c;一个服务器可以与多个客户端进行网络连接&#xff0c;每隔客户端可以向服务器发送命令请求&#xff0c;而服务器则接收并处理客户端发送的命令请求&#xff0c;并向客户端返回命令请求。 通过是一个I/O多路复用技术实现的文件事件处…

3分钟快速实现串口PLC远程下载程序操作说明

3分钟快速实现串口PLC远程下载程序操作说明 搜索蓝蜂物联网官网&#xff0c;即可免费领取样机使用&#xff01;&#xff01;先到先得&#xff01;&#xff01;&#xff01; 一. 适用产品型号 其余型号网关此功能正在开发中&#xff0c;敬请期待。 二. 远程下载功能使用流程 …

数据结构--双向链表专题

目录 1. 双向链表的结构2. 实现双向链表预先的准备初始化尾插、头插尾删、头删查找在pos位置之后插⼊数据删除pos位置的数据 3. 顺序表和双向链表的分析 1. 双向链表的结构 注意&#xff1a;这里的“带头”跟前面我们说的“头结点”是两个概念&#xff0c;为了更好的理解直接称…

Nginx的反向代理:实现灵活的请求转发和内容缓存

一、引言&#xff1a;代理服务器的简介 本节介绍代理服务器的基本配置。学习如何通过不同协议将 NGINX 请求传递给代理的服务器&#xff0c;修改发送到代理服务器的客户端请求标头&#xff0c;以及配置来自代理服务器的响应缓冲。 代理通常用于在多个服务器之间分配负载&…