设计模式-发布订阅模式

55 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第18天,点击查看活动详情

什么是发布-订阅模式

发布—订阅模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知

常见的例子:比如微信公众号,当作者发布了一篇文章,就会立即给你发布一篇文章。

  1. 读者不需要天天都都追着作者询问他有没有更新,当文章更新了读者立马就知道了,读者什么时候看就和作者没有很大的关心了
  2. 读者和作者没有那么强的耦合,有可能作者实现了财富自由由其他人代笔,但是这并不影响读者继续阅读

又比如:异步编程中订阅 ajax 请求的 error、success 等事件,中间的过程我们并不关心,我们只需要知道他完成的时间点。

实现一个订阅发布模式

一个简单的 dom 事件,我们并不知道用户什么时候点击,当body被点击的时候,body节点便会向订阅者发布这个消息。

document.body.addEventListener(
  'click',
  function () {
    alert(2);
  },
  false
);
document.body.click(); // 模拟用户点击

我们来实现一个发布-订阅模式

  1. 建立一个消息中心:负责存储消息与订阅者的对应关系,有消息触发时,负责通知订阅者
  2. 建立订阅者:去消息中心订阅自己需要的消息
  3. 建立发布者:满足条件时,通过消息中心发布消息
class PubSub {
  constructor() {
    // 存放订阅消息
    this.events = {}; // { key: [] }
  }
  // 订阅
  subscribe(event, fn) {
    if (this.events.hasOwnProperty(event)) {
      // 已经订阅了
      this.events[event].push(fn);
    } else {
      // 没有订阅
      this.events[event] = [fn];
    }
  }
  publish(event, agrs) {
    // 去除当前订阅者的订阅回调
    const subscribedEvents = this.events[event];
    if (subscribedEvents && subscribedEvents.length > 0) {
      subscribedEvents.forEach((callback) => {
        callback.call(this, agrs);
      });
    }
  }
}
var pub = new PubSub();
pub.subscribe('order', function (event) {
  console.log('张三', event);
});
pub.subscribe('order', function (event) {
  console.log('李四', event);
});
pub.publish('order', '消费 100¥');
// 张三 消费 100¥
// 李四 消费 100¥

我们再来添加一个取消订阅的方法

  unsubscribe(event, fn) {
    // 删除某个订阅,保留其他订阅
    const subscribedEvents = this.events[event];
    if (subscribedEvents && subscribedEvents.length) {
      if(!fn){
        // 如果 fn 不存在,就清除对应 event 的整个队列
        this.events[event] = [];
        return
      }
      this.events[event] = this.events[event].filter((cb) => cb !== fn);
    }
  }

完整代码

class PubSub {
  constructor() {
    // 存放订阅消息
    this.events = {};
  }
  // 订阅
  subscribe(event, fn) {
    if (this.events.hasOwnProperty(event)) {
      // 已经订阅了
      this.events[event].push(fn);
    } else {
      // 没有订阅
      this.events[event] = [fn];
    }
  }
  // 改写了这里, ...agrs 获取剩余所有参数
  publish(event, ...agrs) {
    // 当前订阅者的订阅回调
    const subscribedEvents = this.events[event];
    if (subscribedEvents && subscribedEvents.length > 0) {
      subscribedEvents.forEach((callback) => {
        callback.call(this, ...agrs);
      });
    }
  }
  unsubscribe(event, fn) {
    // 删除某个订阅,保留其他订阅
    const subscribedEvents = this.events[event];
    if (subscribedEvents && subscribedEvents.length) {
      if (!fn) {
        // 如果 fn 不存在,就清除对应 event 的整个队列
        this.events[event] = [];
        return;
      }
      this.events[event] = this.events[event].filter((cb) => cb !== fn);
    }
  }
}

var pub = new PubSub();
pub.subscribe('order', function (...event) {
  console.log('张三', ...event);
});
pub.subscribe('order', function (event) {
  console.log('李四', event);
});
pub.publish('order', '消费100¥', '买了蛋糕');

// 张三 消费100¥ 买了蛋糕
// 李四 消费100¥

小结

优点

  • 对象之间解耦
  • 异步编程中,可以更松耦合的代码编写

缺点

  • 创建订阅者本身要消耗一定的时间和内存
  • 虽然可以弱化对象之间的联系,多个发布者和订阅者嵌套一起的时候,程序难以跟踪维护

观察者模式:观察者(Observer)直接订阅(Subscribe)主题(Subject),而当主题被激活的时候,会触发(Fire Event)观察者里的事件。

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

观察者模式与发布订阅模式的区别:

  1. 通信方式:在观察者模式中,观察者是知道 Subject 的,Subject 一直保持对观察者进行记录。然而,在发布订阅模式中,先订阅再发布,发布者和订阅者不知道对方的存在。它们只有通过消息代理进行通信。
  2. 组件耦合:在发布订阅模式中,组件是松散耦合的,正好和观察者模式相反。
  3. 同步异步:观察者模式较多时后是同步的,比如当事件触发,Subject 就会去调用观察者的方法。而发布-订阅模式大多数时候是异步的(使用消息队列)。
  4. 使用模式:观察者模式在单个应用程序实现,而发布-订阅更像交叉应用模式。

观察者模式

为了区分发布订阅模式,我们也来实现一个观察者模式

class Observer {
  constructor() {
    // 存放订阅者
    this.observers = [];
    // {
    //     observer: obj,
    //     action: () => {}
    //  }
  }

  addObserver(observer, action) {
    // 将观察者和回调放入数组
    this.observers.push({ observer, action });
  }

  notify(...args) {
    // 执行每个观察者的回调
    this.observers.forEach((item) => {
      const { observer, action } = item;
      action.call(observer, ...args);
    });
  }
}

const observer = new Observer();

// 添加观察者
observer.addObserver({ name: '张三' }, function (msg) {
  console.log(this.name + '赚了' + msg);
});
observer.addObserver({ name: '李四' }, function (msg) {
  console.log(this.name + '赚了' + msg);
});

// 通知所有观察者
observer.notify('一个小目标');
// 张三赚了一个小目标
// 李四赚了一个小目标