发布订阅模式

388 阅读2分钟

发布订阅模式。

案例分析 (以制作一个简易播放器为例,是包括概念,不包括具体逻辑的实现)

播放器的主要步骤:

  1. 初始化播放器 -> 播放 -> 播放中 -> 结束

转化为代码: (demo1.js)

class Player{
  init(){}
  play(){}
  stop(){}
}

这些步骤中有些步骤是非常耗时的,因此无法同步调用。比如说:初始化。(如果播放器初始化未完成时,是无法调用播放方法的)

转化为代码: (demo2.js)

// 由于每个步骤可能是非常耗时的操作。
class Player {
  constructor(){
    this.isInitSuccess = false;
  }
  init() {
    setTimeout(() => { // 模仿耗时操作,假设两秒钟之后,播放器初始化完成
      this.isInitSuccess = true;
    }, 2000);
  }
  play() {
      if (!this.isInitSuccess) throw Error('播放器为初始化')  
  }
  stop() {}
}

let player = new Player()
// 代码会报错,因为init需要时间
player.init()
player.play()

这种问题怎么解决呢? 可以采用发布订阅模式; 转化为代码: (demo3.js)

class Player {
  constructor() {
    // initsuccess
    this.events = {};
    this.isInitSuccess = false;
  }
  init() {
    setTimeout(() => { // 模仿耗时操作,假设两秒钟之后,播放器初始化完成
      this.isInitSuccess = true;
      this.emit('initsuccess','')
    }, 2000);
  }
  ...
  on(eventNamve,cb){
    this.events[eventNamve] = (this.events[eventNamve] ? this.events[eventNamve].push(cb) : [cb]);
  },
  emit(eventNamve,...args){
        (this.events[eventNamve] || []).forEach(eventCallBack => {
          eventCallBack(...args)
        });
  }
}
let player = new Player()
player.init();
player.on('initsuccess',()=>{
  console.log('初始化完成')
})

什么叫发布订阅模式?

上述代码中,我们使用on注册一个函数,内部emit在适合的时机触发。我们把这种思路的代码,称之为 "发布订阅" 模式。 其中,触发on方法,我们称之为 '订阅者',触发emit,我们称之为 '发布者'。

案例扩展

  1. 增加了'播放中'与'播放完成'订阅者

转化为代码: (demo4.js)

class Player {
  constructor() {
    this.events = {};// initsuccess
    this.isInitSuccess = false;
    this.timer = null;
    this.time = 10; 
  }
  init() {
    setTimeout(() => { // 模仿耗时操作,假设两秒钟之后,播放器初始化完成
      this.isInitSuccess = true;
      this.emit('initsuccess', '')
    }, 2000);
  }
  play() {
    if (!this.isInitSuccess) throw Error('播放器为初始化')
    this.time = 10;
    this.timer = setInterval(() => {
       this.time--;
       if (this.time===0){
         clearInterval(this.timer) 
         this.emit('playiend','')
       }
       this.emit('playing', this.time) 
    }, 1000);
  }
  on(eventNamve, cb) {
    this.events[eventNamve] = (this.events[eventNamve] ? this.events[eventNamve].push(cb) : [cb]);
  }
  emit(eventNamve, ...args) {
    (this.events[eventNamve] || []).forEach(eventCallBack => eventCallBack(...args));
  }
}
let player = new Player()
player.init();
player.on('initsuccess', () => {
  console.log('初始化完成')
  player.play()
});
player.on('playing', (time) => {
  console.log('剩余播放时间',time)
})
player.on('playend', (time) => {
  console.log('播放完成')
})

观察者模式

观察者模式是发布订阅模式一种简化的应用。

看案例

class Subject { // 被观察者的类  被观察者 需要将观察者收集起来
  constructor(name) {
    this.name = name;
    this.state = '非常开心'
    this.observers = [];
  }
  attach(o) { //  小宝宝 进行收集
    this.observers.push(o); // on
  }
  setState(newState) {
    this.state = newState;
    this.observers.forEach(o => o.update(this.name, newState)) // emit
  }
}
class Observer { // 观察者
  constructor(name) {
    this.name = name;
  }
  update(s, state) {
    console.log(this.name + ":" + s + '当前' + state);
  }
}
// vue 数据变了(状态) 视图要更新 (通知依赖的人)  

let s = new Subject('小宝宝');
let o1 = new Observer('爸爸');
let o2 = new Observer('妈妈');
s.attach(o1)
s.attach(o2)
s.setState('不开心了')

特点

  1. 只有一个订阅事件(attach)。
  2. 增加状态的概念,案例中的:小宝宝的状态(开心,不开心)。
  3. 形成闭环概念。 (小宝宝不开心——> 通知爸爸妈妈 ---> 爸爸妈妈哄小宝宝)

发布订阅与观察者模式对比: image.png