再手写一次发布订阅和观察者

简介: 发布订阅模式和观察者模式是开发中常用的设计模式和思想,利用它们可以做到数据更高级的通信,当然在Vue和React等框架中,也用到了它们,本篇就来说一下它们的实现原理并手写代码。

网络异常,图片无法展示
|

发布订阅模式和观察者模式是开发中常用的设计模式和思想,利用它们可以做到数据更高级的通信,当然在Vue和React等框架中,也用到了它们,本篇就来说一下它们的实现原理并手写代码。


发布订阅模式


原理

在软件架构中,发布-订阅 是一种消息范式,消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者)。而是将发布的消息分为不同的类别,无需了解哪些订阅者(如果有的话)可能存在。同样的,订阅者可以表达对一个或多个类别的兴趣,只接收感兴趣的消息,无需了解哪些发布者(如果有的话)存在。 —— 维基百科

发布订阅模式从它的概念里就可以看出来特点:发布者 发出的消息,不会发送给特定的 订阅者订阅者 不会直接接收发布者的消息,反过来也是,这意味着发布者和订阅者不知道彼此的存在。 那他们之间是怎么通信的呢?

原来在它们中间存在一个“第三者”,它被称之为 调度中心事件通道,它维持着 发布者订阅者 之间的联系,过滤所有 发布者 传入的消息并相应地分发它们给 订阅者

所以现在我们知道了,完成发布订阅的整个流程需要三个角色:

  1. 发布者
  2. 调度中心
  3. 订阅者


实现

在JS中,它们之间的逻辑是这样的,订阅者调度中心 订阅指定的事件,发布者调度中心 发布指定的事件,调度中心 通知 订阅者订阅者 收到消息,当然一个发布者事件可能会有多个 订阅者

从这个逻辑里,我们可以列出如下代码:

class EventEmitter{
  constructor(){
    // 汇总所有的事件和监听
    this.listeners = {};
  }
  /** 绑定事件的监听者
   * @param {String} eventType 事件类型
   * @param {Function} cb 回调函数
   */
  on(eventType, cb){
    // 如果还没有监听者就先初始化一下
    if(!this.listeners[eventType]){
        this.listeners[eventType] = [];
    }
    // 塞入订阅者的回调
    this.listeners[eventType].push(cb);
  }
  /** 发布事件
   * @param {String} eventType 事件类型
   * @param {Function} args 参数列表,把emit传递的参数赋给回调函数
   */
  emit(eventType, ...args){
    // 如果已经订阅了事件,就执行
    if(this.listeners[eventType]){
      this.listeners[eventType].forEach(cb => {
        cb(...args)
      })
    }
  }
  /** 解绑事件的监听者
   * @param {String} eventType 事件类型
   * @param {Function} cb 回调函数
   */
  off(eventType, cb){
    // 如果当前事件存在监听者,就移除它
    if(this.listeners[eventType]){
      const index = this.listeners[eventType].findIndex(fn => fn == cb);
      if(index !== -1){
        this.listeners[eventType].splice(index, 1);
      }
      if(!this.listeners[eventType].length){
        // 如果没有事件监听它了,就直接删除这个事件类型
        delete this.listeners[eventType];
      }
    }
  }
}

这样的话,一个简单的发布订阅就实现了,我们就可以这样使用它:

// 实例化一个发布订阅
const ee = new EventEmitter();
// 注册一个监听者
ee.on("speak", function(){
  console.log("我讲话了!");
});
ee.emit("speak");
ee.on("speak", function(msg){
  console.log(`我说,${msg}`);
});
ee.emit("speak","你在干啥?");
// output: 
// 我讲话了!
// 我讲话了!
// 我说,你在干啥?

上面打印两次 “我讲话了” 是因为总共注册了2个 “speak” 的监听者,这样一个简易的发布订阅就成功啦!


观察者模式


原理

观察者模式发布订阅模式 不同,观察者模式 是没有 调度中心 的存在的,它是直接监听的对象,当一个对象的状态发生变化时,所有依赖于它的对象都将得到通知,并自动更新,它也是一种一对多的关系。


实现

那没有 调度中心 也就意味着一个对象被直接监听了,此时又得保证在移除的时候可以找到特定的监听者,所以在观察者和被观察者的定义里都需要一个类似唯一id的标识符,我们来下一下它的逻辑:

let obser_ids=0;
let obsed_ids=0;
// 观察者
class Observer {
  constructor(){
    this.id = obser_ids++;
  }
  // 数据发生变化后的回调
  update(...args){
    console.log(...args)
  }
}
// 被观察者
class Observed {
  constructor(){
    this.observers = [];
    this.id = obsed_ids++;
  }
  // 添加观察者
  addObserver(observer){
    this.observers.push(observer);
  }
  // 通知所有观察者
  notify(...args){
    this.observers.forEach(observer => {
      observer.update(...args);
    });
  }
  //移除观察者
  deleteObserver(observer){
    this.observers = this.observers.filter(o => {
      return o.id != observer.id;
    });
  }
}

观察到变化之后,遍历观察者数组执行回调函数,删除观察者通过唯一标识符判定进行删除,一个简单的观察者就实现了,我们可以测试一下:

// 实例化一个被观察者
let od = new Observed();
// 实例化两个观察者
let or1 = new Observer();
let or2 = new Observer();
// or1 和 or2 观察 od
od.addObserver(or1);
od.addObserver(or2);
// 通知所有观察者
od.notify("通知了!");
// output: 
// 通知了!
// 通知了!

可见两个观察者都检测到了被观察者的变化,例子成功!

目录
相关文章
|
6月前
|
设计模式
设计模式-观察者(发布订阅)模式
设计模式-观察者(发布订阅)模式
|
6月前
|
消息中间件 设计模式 前端开发
【面试题】说说你对发布订阅、观察者模式的理解?区别?
【面试题】说说你对发布订阅、观察者模式的理解?区别?
104 0
|
6月前
行为型 观察者模式(发布订阅)
行为型 观察者模式(发布订阅)
39 0
|
6月前
|
设计模式 监控 容器
设计模式-观察者模式(观察者模式的需求衍变过程详解,关于监听的理解)
设计模式-观察者模式(观察者模式的需求衍变过程详解,关于监听的理解)
|
设计模式 消息中间件 Java
SpringBoot事件监听机制及观察者/发布订阅模式详解
介绍观察者模式和发布订阅模式的区别。 SpringBoot快速入门事件监听。 什么是观察者模式? 观察者模式是经典行为型设计模式之一。 在GoF的《设计模式》中,观察者模式的定义:在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会自动收到通知。如果你觉得比较抽象,接下来这个例子应该会让你有所感觉:
|
消息中间件 设计模式 Java
SpringBoot事件监听机制及观察者模式/发布订阅模式
SpringBoot事件监听机制及观察者模式/发布订阅模式
391 0
|
设计模式
关于观察者模式/发布订阅模式我所知道的
关于观察者模式/发布订阅模式我所知道的
105 0
|
设计模式 前端开发 调度
简化理解:发布订阅
简化理解:发布订阅
|
设计模式 JavaScript 调度
它们不一样!透析【观察者模式】和【发布订阅模式】
观察者模式常常会和发布订阅模式一起哪来比较,它们二者同样重要。
|
Java Spring
观察者模式和发布订阅模式的区别见解
观察者模式和发布订阅模式的区别见解
354 0