发布订阅与观察者

276 阅读3分钟

发布订阅与观察者模式总是被混淆,不容易分辨,其实这两种模式最初都是同一种,之后进行了一些发展与演变,才有了发布订阅这种解耦的方式,就自己的理解谈一谈这两种方式的具体区别。

两者的区别

发布订阅可以说是观察者模式的一种升级

观察者模式核心是我知道你的存在,当发生了变化时我直接通知大家发生的变化。 观察者模式

发布订阅模式核心是我不知道其他人是否存在,我需要告诉第三方有事件了,第三方负责去通知各个订阅人,同理各个订阅人也只是订阅了,具体会不会发生这个事件订阅人也不知道。

发布订阅模式

观察者模式是松耦合的,发布订阅是解耦合的。

解耦合当然是好事情了,但是还是需要去看具体的场景了,如果彻底解耦就需要第三方来帮助我们对接,那么无疑会增加我们的代码

如果是大型系统的对接,我们就需要使用解耦合的场景,未来我们也不知道会有那些人来使用,会有谁接入进来,所以需要一个第三方平台来管控,是比较便于后续系统的继续拓展的。

如果是我们自己内部代码,那么松耦合也是可以接受的,我们在编写代码就能掌控到全局,为了代码模块今后的拓展性,采用松耦合的方式也完全是ok的。

当发生取消操作时(不订阅),观察者模式主体是需要亲自操作的,而发布订阅模式发布者是不需要感知的。

JS简单实现两种方式

熟悉ts,推荐使用ts去实现,对于理解原理会更好一点。

观察者模式

核心代码

// 观察者
class Observer {
  update(payload) { }
}

// 事件主体
class Subject {
  constructor() {
    this._observers = []
  }

  add(observer) {
    this._observers.push(observer)
  }

  remove(observer) {
    const removeIndex = this._observers.indexOf(observer);
    if (removeIndex !== -1) {
      this._observers.splice(removeIndex, 1);
    }
  }

  notify(payload) {
    this._observers.forEach(observer => observer.update(payload));
  }
}

使用示例

创建一个前端主题,并创建出多个开发者,最终前端更新主题,直接通知到开发者

class FEObserver extends Observer {
  constructor(name) {
    super();
    this.name = name;
  }

  update(payload) {
    console.log(`${this.name}学习了-${payload.type}`);
  }
}

const feSubject = new Subject<FEPayloadType>();
const fe1 = new FEObserver('fe1');
const fe2 = new FEObserver('fe2');

feSubject.add(fe1);
feSubject.add(fe2);

feSubject.notify({ type: 'CSS' });
// fe1学习了-CSS
// fe2学习了-CSS

feSubject.remove(fe2);

feSubject.notify({ type: 'JS' });
// fe1学习了-JS

发布订阅模式

核心代码

class EventEmitter {
  constructor() {
    this.c = new Map();
  }

  // 订阅指定的主题
  subscribe(topic, ...handlers) {
    let topics = this.c.get(topic);
    if (!topics) {
      this.c.set(topic, topics = []);
    }

    topics.push(...handlers);
  }

  // 取消订阅主题
  unsubscribe(topic, handler) {
    if (!handler) {
      return this.c.delete(topic);
    }

    const topics = this.c.get(topic);
    if (!topics) {
      return false;
    }

    const removeIndex = topics.indexOf(handler);
    if (removeIndex < 0) {
      return false;
    }

    topics.splice(removeIndex, 1);
    if (topics.length === 0) {
      this.c.delete(topic);
    }

    return true;
  }

  // 发布主题
  publish(topic, ...args) {
    const topics = this.c.get(topic);
    if (!topics) {
      return null;
    }

    return topics.map(handler => {
      try {
        return handler(...args);
      } catch (e) {
        console.error(e);
        return null;
      }
    });
  }
}

使用示例

创建一个ts学习主题,进行一下订阅,之后统一发布到EventCenter

const eventCenter = new EventEmitter();

const subscriberTs1 = (content) => {
  console.log(`subscriberTs1 receive: ${content}`);
};
const subscriberTs2 = (content) => {
  console.log(`subscriberTs2 receive: ${content}`);
};

// 订阅事件
eventCenter.subscribe('ts', subscriberTs1);
eventCenter.subscribe('ts', subscriberTs2);

// 发布事件
eventCenter.publish('ts', '发布了一篇ts文章');
// subscriberTs1 receive: 发布了一篇ts文章
// subscriberTs2 receive: 发布了一篇ts文章

// 取消订阅
eventCenter.unsubscribe('ts', subscriberTs1);

// 发布事件
eventCenter.publish('ts', '发布了二篇ts文章');
// subscriberTs2 receive: 发布了二篇ts文章

// 取消订阅
eventCenter.unsubscribe('ts');

// 发布事件
eventCenter.publish('ts', '发布了三篇ts文章');