观察者模式

在观察者模式中,多个观察者监听同一个目标对象。当目标对象的状态发生改变的时候,通知所有观察者,进行更新操作。 它们之间是直接进行通信的,由发布者直接通知观察者进行更新。

image.png

发布/订阅模式

消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者)。
而是将发布的消息分为不同的类别(消息过滤),无需了解哪些订阅者(如果有的话)可能存在。
同样的,订阅者可以表达对一个或多个类别的兴趣,只接收感兴趣的消息,无需了解哪些发布者(如果有的话)存在。
——维基百科
发布/订阅模式和观察者模式最大的区别就是,发布者和订阅者并不会直接进行通信,而是交给一个消息中间件——消息代理(message broker)。
订阅者只需要告诉消息代理,它需要接受哪些信息。发布者只需要告诉消息代理,它发送了什么信息,然后消息代理将消息过滤,发送给订阅者。双方并不关心对方是否存在。
消息过滤
那么消息代理是怎么进行过滤的呢?其实有两种方式:
基于主题进行过滤
基于内容进行过滤
基于主题
发布者负责对消息进行分类,发布到主题或信息通道上。订阅了同一主题的所有订阅者,都将接收到主题里的所有内容。
基于内容
订阅者负责对消息进行分类,只有消息的属性或内容满足订阅者定义的条件时,才会投递到该订阅者。
image.png
有些系统中支持两种混合:发布者发布内容到主题,订阅者基于内容的订阅注册到一个或多个主题。

两者对比

在观察者模式里是松耦合的,而在发布/订阅模式里是解耦的。
观察者模式里大多是同步操作,而发布/订阅模式大多是异步操作。
在观察者模式里Observer是知道Publisher的,Publisher也一直保持对Observer的记录。而发布/订阅模式里,它们并不知道对方的存在。并且只通过消息代理进行通信。
观察者模式大多是存在应用程序内部,而发布/订阅模式存在于两个应用程序之间。
——————————————

例子——Event Bus

在Vue中如果在不使用Vuex的情况下,实现两个组件通信,可以使用Event Bus。它的作用就是作为一个全局事件总线,来实现两个相隔千里的组件相互通信。其实现的原理就是使用发布/订阅模式。让全局事件总线来充当消息代理。

实现一个Event Bus

  1. // 本质上EventBus就是一个Vue实例
  2. const bus = new Vue();
  3. export default bus;
  1. import bus from 'EventBus所在的路径';
  2. // 注册到Vue原型上
  3. Vue.prototype.bus = bus;
  1. // 监听"sayHi"事件,第二参数是监听到该事件时的执行回调函数
  2. this.bus.$on('sayHi', callback);
  1. // 派发"sayHi"事件,第二个参数是要传进"回调函数"的参数
  2. this.bus.$emit('sayHi', params);

来实现一个单独的EventBus

  1. class EventBus {
  2. constructor() {
  3. // 使用Map来存储每一个EventName对应的所有回调函数
  4. this.handlers = new Map();
  5. }
  6. // 监听目标事件,接收事件名和回调函数
  7. on(eventName, callback) {
  8. const { handlers } = this;
  9. // 如果EventName对应的回调函数栈存在,则继续往里面存新的回调函数
  10. if (handlers.get(EventName)) {
  11. handlers.get(EventName).push(callback);
  12. } else {
  13. // 否则创建新的回调函数栈
  14. handlers.set(EventName, [callback]);
  15. }
  16. }
  17. // 触发目标事件,传入事件名和参数
  18. emit(eventName, ...args) {
  19. const { handlers } = this;
  20. // 先查询该方法名是否存在
  21. if (handlers.get(eventName)) {
  22. // 存在则调用所有订阅者的回调函数
  23. handlers.get(eventName).forEach(callback => callback(...args));
  24. } else {
  25. // 不存在就抛出异常
  26. throw new Reference('Methods not exits');
  27. }
  28. }
  29. // 移除某个事件里的指定回调函数
  30. off(eventName, callback) {
  31. const { handlers } = this;
  32. const index = handlers.get(eventName).indexOf(callback);
  33. if (index > -1) {
  34. handlers.get(eventName).splice(index, 1);
  35. } else {
  36. throw new Reference('Methods not exits');
  37. }
  38. }
  39. // 注册单次的监听器
  40. once(eventName, callback) {
  41. const wrapper = (...args) => {
  42. callback(..args);
  43. this.off(eventName, callback);
  44. }
  45. this.on(eventName, wrapper);
  46. }
  47. }