event-bus

观察者模式

概念

在此种模式中,一个目标对象管理所有相依于它的观察者对象,并且在它本身的状态改变时 主动 发出通知。这通常透过呼叫各观察者所提供的方法来实现。此模式在前端最常见的应用就是实现响应式数据.

简单来说, 观察者模式就是, 一个对象(被观察者)的状态发生改变时,会通知所有依赖它的对象(观察者),这两者是直接关联的

意图:定义对象间的 一对多 的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作

何时使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。

如何解决:使用面向对象技术,可以将这种依赖关系弱化

关键代码:在抽象类里有一个 ArrayList 存放观察者们

优点:

  1. 观察者和被观察者是抽象耦合的
  2. 建立一套触发机制。

缺点:

  1. 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
  2. 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
  3. 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

这些缺点如何完善呢? 编码时注意避免循环, 通过发布订阅模式通知状态, 但是 1 还是不能解决

使用场景:

注意事项:

  1. JAVA 中已经有了对观察者模式的支持类。
  2. 避免循环引用。
  3. 如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。

实现

const Subject = (() => {
  const observers = [];
  const addOb = (ob) => {
    observers.push(ob);
  };
  const notify = () => {
    for (let ob of observers) {
      if (typeof ob.update === 'function') {
        ob.update();
      }
    }
  };
  return {addOb, notify};
})();

let subA = {
  update: () => {
    console.log('updateSubA');
  }
},
    subB = {
      update: () => {
        console.log('updateSubB');
      }
    };
Subject.addOb(subA);    //添加观察者subA
Subject.addOb(subB);    //添加观察者subB
Subject.notify();       //通知所有观察者 'updateSubA updateSubB'

MVC 架构

观察者模式的经典运用

Java 内置的观察者模式

head first P65

https://awesome-programming-books.github.io/design-pattern/HeadFirst设计模式.pdf

101

简单来说, 就是除了在通知的时候传输数据, 还可以传输 class 本身, 然后通过 getter 从 class 上获取数据.

这种模式, 相对于传统的, 可以形象的称之为

观察者模式中的设计原则

  1. 在观察者模式中, 会改变的是主题的状态, 以及观察者的数目和类型. 用这个模式, 你可以改变依赖于主题状态的对象, 却不必改变主题. 这就叫提前原则.
  2. 主题于观察者都使用接口, 观察者利用主题的接口向主题注册, 而主题利用观察者接口通值观察者, 这样可以让两者之间运作正常, 同时又是松耦合的.
  3. 观察者模式利用组合, 将许多观察者组合进主题中.

发布订阅模式

概念

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

发布者状态更新时,发布某些类型的通知,只通知订阅了 相关类型的订阅者。发布者和订阅者之间是没有直接关联的。

img

如上图所示,发布者与订阅者直接不是互相依赖和关联的,两者之间有一个通信结构(事件通道)。这个事件通道会处理发布者发布的不同类型的通知,并且将这些通知发送给相应的订阅者。

实现

const PubSub = (() => {
  const topics = {};  //保存订阅主题
  const subscribe = (type, fn) => {   //订阅某类型主题
    if (!topics[type]) {
      topics[type] = [];
    }
    topics[type].push(fn);
  };
  const publish = (type, ...args) => {    //发布某类型主题
    if (!topics[type]) {
      return;
    }
    for (let fn of topics[type]) {      //通知相关主题订阅者
      fn(...args);
    }
  };
  return {subscribe, publish};
})();

let subA = {type: 'event1'},
    subB = {type: 'event2'},
    subC = {type: 'event1'};

PubSub.subscribe(subA.type, () => console.log(`update eventType: ${subA.type} subA`));   //订阅者A订阅topic1
PubSub.subscribe(subB.type, () => console.log(`update eventType: ${subB.type} subB`));   //订阅者B订阅topic2
PubSub.subscribe(subC.type, () => console.log(`update eventType: ${subC.type} subC`));   //订阅者C订阅topic1
PubSub.publish(subA.type);  //发布topic通知,通知订阅者A、C

手写一个 EventEmitter

JavaScript.md

class EventEmitter {
  constructor() {
    this.events = {};
  }

  on(event, callback) {
    let callbacks = this.events[event] || [];
    callbacks.push(callback);
    this.events[event] = callbacks;

    return this;
  }

  off(event, callback) {
    let callbacks = this.events[event];
    this.events[event] = callbacks && callbacks.filter(fn => fn !== callback);

    return this;
  }

  emit(event, ...args) {
    let callbacks = this.events[event];
    callbacks.forEach(fn => {
      fn(...args);
    });

    return this;
  }

  once(event, callback) {
    let wrapFun = (...args) => {
      callback(...args);

      this.off(event, wrapFun);
    };
    this.on(event, wrapFun);

    return this;
  }
}

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

联系

广义上来说,观察者模式和发布 - 订阅模式,都是一个对象的状态发生变化,通知相关联的对象。所以广义上来说,这两种模式是相似的,正如《Head First 设计模式》所说。

区别

img

先来看一张图,左边是观察者模型,右边是发布 - 订阅者模型。结合这个图和上文的分析,我们可以总结下这两者的区别。