面试官:观察者模式和发布订阅者模式的区别是什么?

669 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第14天,点击查看活动详情

前言

观察者模式(observer)和发布订阅者模式(Publisher--Subscribe)的区别到底是什么?常常会搞混,因为他们的差别很细微

区别

发布订阅 如上图所示:

  1. 耦合度差异
  • 观察者模式 中的观察者和被观察者之间还存在耦合,被观察者还是知道观察者的;
  • 发布-订阅模式 中的发布者和订阅者不需要知道对方的存在,他们通过消息代理来进行通信,解耦更加彻底;
  1. 关注点不同:
  • 观察者模式主要是实现观察者和被观察者的
  • 发布-订阅模式则主要是实现调度中心的

使用场景

观察者模式

例如:群发消息
观察者模式比较难理解的点就是到底谁是被观察者,比如在这个群发消息里面,比如一个群里面有小A,小B,小C,现在小A在群里发送一个消息,那么谁是观察者,谁是被观察者,这里面观察者者,小A(因为小A也会看到消息更新),小B,小C,被观察者是消息对话框, 接下来我们用代码来实现一下这个场景

     class Subject {
  constructor() {
    this.observes = [];
  }

  // 添加观察者
  addObserves(observe) {
    this.observes.push(observe);
  }

  // 通知观察者
  notify(message) {
    this.observes.forEach((observe) => {
      observe.receiveNotification(message);
    })
  }
}


class Observe {
  constructor(name) {
    this.name = name;
  }

  receiveNotification(message) {
    console.log(`${this.name}收到来自---${message}`);
  }
}

// 定义观察者
const A = new Observe('小A');
const B = new Observe('小B');
const C = new Observe('小C');

// 定义被观察者
const dialogWindow = new Subject();
dialogWindow.addObserves(A);
dialogWindow.addObserves(B);
dialogWindow.addObserves(C);
dialogWindow.notify('小A发送的消息'); 

发布订阅模式

例如 Node.js EventEmitter 中的 on 和 emit 方法;Vue 中的 $on$emit 方法。 例如 Vue中的双向绑定 他们都有一个共同的特点,就相当于实现了一个平台。比如youtube上的订阅功能,youtube相当于实现了一个发布订阅功能,订阅者只需要点击订阅功能,作者只需要发布作品,youtube就会通知订阅者作者发布了新视频,而这里的youtube平台就是实现了发布订阅模式

实现

实现双向绑定

let data = { name: 'my name', age: 12 };
observe(data);
function observe(data) {
  if (!data || typeof data !== 'object') {
    return;
  }
  Object.keys(data).forEach((key) => {
    dataChange(data,key,data[key]);
  })
}

function dataChange(data, key, value) {
  observe(value);
  Object.defineProperty(data, key, {
    enumerable: true,
    configurable: true,
    get: function () {
      return value;
    },
    set: function (newValue) {
      console.log('data发生了变化', newValue);
      value = newValue;
    }
  })
}

实现EventEmitter

class EventEmitter{
  constructor() {
    // 存储事件的数据结构,为了方便查找,使用对象(字典)
    this._cache = {};
  }

  // 绑定
  on(eventName, callback) {
    // 将同一类型事件放到一个数组中
    // 如果eventName存在
    let fns = this._cache[eventName];
    if (fns && Array.isArray(fns)) {
      if (!fns.includes[callback]) {
        fns.push(callback);
      } 
    }else {
      fns = [callback];
    }
    this._cache[eventName] = fns;
    // 支持链式调用
    return this;
  }

  emit(eventName, data) {
    let fns = this._cache[eventName];
    if (Array.isArray(fns)) {
      fns.forEach((fn) => {
        fn(data);
      })
    }
    return this;
  }

  // 解绑
  off(eventName, callback) {
    // 解绑如果传入callback,那就是解绑eventName的callback方法;如果不传callback,那就是解绑eventName的全部方法
    let fns = this._cache[eventName];
    if (Array.isArray(fns)) {
      if (callback) {
        let index = fns.indexOf(callback)
        if (index !== -1) {
          fns.splice(index, 1);
        }
      } else {
        fns.length = 0;
      }
     
    }
    
    return this;
  }

}


const event1 = new EventEmitter();
event1.on('input', function input(data) {
  console.log('输入'+data);
});

event1.on('output', function output(data) {
  console.log('输出'+data);
});

console.log(event1);

event1.emit('input', 1)  // 输入1
event1.emit('output',2)  // 输出2

参考