浅析 观察者模式(Observer) 和 发布(Publish)/订阅(Subscribe)模式

1,313 阅读3分钟

浅析 观察者模式(Observer) 和 发布(Publish)/订阅(Subscribe)模式

对于观察者模式 Observer 和 发布 Publish / 订阅 Subscribe 模式,这里简单做个记录, 最大的区别就是 调度中心的不同。

观察者模式 Observer

这里就简单做一个情景分析: 小明很喜欢隔壁 的小美,小美长得老漂漂亮了, 但是小明有点⁄(⁄ ⁄•⁄ω⁄•⁄ ⁄)⁄(害羞),并且有点孤傲,不想刻意去打招呼,就想着能不能 “不经意”的去 认识一下,这点让它有点烦恼。这天小明在家思考🤔这个不经意怎么制造的时候,忽然,有急促的敲门声,烦得很ε=ε=ε=(#>д<)ノ, 于是想去battle一下,打开门发现是小美的快递,于是 一个 “不经意”的想法出现了, 小明想让 快递员老张 每次去给小美送快递的时候 给他报个信, (^▽^)

首先来个简单的快递员类

// 五星级快递员
class DeliverGuy {
  delivery(who) {
    console.log(`${new Date().toLocaleString()} 嘿咻嘿咻 给 ${who} 送货`)
  }
}
  • 小明:哎,老张 我有个小事情,help一下。
  • 老张:阿巴阿巴阿巴
  • 小明:两瓶快乐水
  • 老张:好嘞ヽ( ̄▽ ̄)و

就这样老张 做起了副业,并秉承这做大做强的 心理,觉得以后可以好好发展的 态度, 扩宽了自己的副业

class Person {
  constructor(name) {
    this.name = name
  }

  log(str) {
    console.log(`${new Date().toLocaleString()} ${this.name}:  ${str}`)
  }
}

class DeliverGuy extends Person {
  constructor(props) {
    super(props)
  }
  delivery(who) {
    // 先去跑一遍贯彻着注册的逻辑
    this.observerList.forEach(ob => {
      this.log(`${ob.p.name},麻溜的,我要给 ${who} 送货了,一天天的, 挂了嗷, do...`)
      ob.cb && ob.cb()
      this.log(`下次得加钱 -。-, 先去送货`)
    })
    this.log(`嘿咻嘿咻 给 ${who} 送货`)
  }

  observerList = []
  addExtraObserver(p, cb) {
    this.observerList.push({
      p,
      cb
    })
  }
}

class XiaoMing extends Person {
  constructor(props) {
    super(props)
  }
  OutForXiaoMei = () => {
    this.log('I\' ready for beautiful day now')
  }
}

let LaoZhang = new DeliverGuy('老张')
let XiaoMing = new XiaoMing('小明')

LaoZhang.addExtraObserver(XiaoMing, XiaoMing.OutForXiaoMei)
LaoZhang.delivery('小美')
2022/1/12 上午11:37:01 老张:  小明,麻溜的,我要给 小美 送货了,一天天的, 挂了嗷, do...
2022/1/12 上午11:37:01 小明:  I'm ready for beautiful day now
2022/1/12 上午11:37:01 老张:  下次得加钱 -。-, 先去送货
2022/1/12 上午11:37:01 老张:  嘿咻嘿咻 给 小美 送货

以上 老张快递员 后面扩宽的业务方法 addExtraObserver 就是 典型的观察者模式处理方式, 把观察者注册方法都注册到了 自己的处理逻辑中, 耦合到了一起。

观察者模式.jpg

发布订阅者模式

发布订阅者模式和观察者模式最大区别就是 调度不同, 发布订阅这模式 是对 观察者模式的 解耦。 下面就简单写一个 类vue bus的 发布订阅 class

/**
 * Vue 的on off emit 事件 做消息中心
 */
class VueBus {
  static ins;

  constructor() {
    //  事件通道调度中心
    this._events = Object.create(null);
  }
  $on(event, fn) {
    if (Array.isArray(event)) {
      event.map(item => {
        this.$on(item, fn);
      });
    } else {
      (this._events[event] || (this._events[event] = [])).push(fn);
    }
    return this;
  }
  $once(event, fn) {
    function on() {
      this.$off(event, on);
      fn.apply(this, arguments);
    }
    on.fn = fn;
    this.$on(event, on);
    return this;
  }
  $off(event, fn) {
    if (!arguments.length) {
      this._events = Object.create(null);
      return this;
    }
    if (Array.isArray(event)) {
      event.map(item => {
        this.$off(item, fn);
      });
      return this;
    }
    const cbs = this._events[event];
    if (!cbs) {
      return this;
    }
    if (!fn) {
      this._events[event] = null;
      return this;
    }
    let cb;
    let i = cbs.length;
    while (i--) {
      cb = cbs[i];
      if (cb === fn || cb.fn === fn) {
        cbs.splice(i, 1);
        break;
      }
    }
    return this;
  }
  $emit(event) {
    let cbs = this._events[event];
    if (cbs) {
      const args = [].slice.call(arguments, 1);
      cbs.map(item => {
        args ? item.apply(this, args) : item.call(this);
      });
    }
    return this;
  }
}

const vueBus = new VueBus()

vueBus.$on('test', function(s) {
    console.log('test:', s)
})
vueBus.$emit('test', '-.-')

以上就是简单的 vueBus events的简单实现, 在我看来和观察者 有着明显的区别就是, 发布者和订阅者 是互不关心的 整体, 有着自己的独立的逻辑, 通知订阅者 是依靠 调度中心。 下面 关系图

发布者订阅模式.jpg

anyWhy 下面总结下

耦合

耦合, 我个人觉得观察者模式和 发布订阅模式的最大区别就是 耦合, 具体目标对象中会耦合 注册进入 观察者的 附带逻辑。而 发布订阅者不会, 二者完全解耦。

观察者比较概念的解释是,目标和观察者都是基类,目标维护观察者的一系列的方法,观察者只提供接口。 具体的观察者和具体的目标 即成各自的基类,然后具体观察者把自己注册到具体目标里,在具体目标发生变化后,注意,是具体目标去调度具体观察者的逻辑哦

简单整理下,把一些 观察者模式里面 具象化的 逻辑提取出来

/**
 * 简单的 观察者类
 */
class Observer {
  constructor() {
    this.observerList = new Array()
  }

  addObserver(cb) {
    this.observerList.push(cb);
  }

  countObserver() {
    return this.observerList.length
  }

  getObserver(index) {
    if(index > -1 && index < this.observerList.length){
      return this.observerList[index];
    }
  }

  indexOfObserver(cb, startIndex){
    var i = startIndex ? startIndex : 0;
    while(i < this.observerList.length){
      if(this.observerList[i] === cb){
        return i
      }
      i++;
    }
    return -1
  }

  removeAtObserver(index){
    this.observerList.splice(index, 1)
  }

  emptyObserver() {
    this.observerList.splice(0)
  }

  /**
   * 通知观察者
   */
  notifyObersver() {
    console.log(this)
    for (let i = 0; i < this.countObserver(); i++) {
      const observer = this.getObserver(i);
      // 这里可以改进下, this的指向问题
      observer()
    }
  }
}

// 目标
class Subject extends Observer {
  constructor() {
    super()
  }
}

var s = new Subject()
s.addObserver(function() {
  console.log(this)
  console.log(1)
})

另外 mobx 里面的 makeObserver 也是一种 观察者模式, 下面是一个简单的 利用ES6 Proxy和Reflct + 闭包 的简单实现


// 用symbol 做一个唯一变量
let _observer_ = Symbol('[[observer]]');

function makeObservable(target) {
  // 初始化 target 上自己的 _observer_ list
  target[_observer_] = [];

  // 将 handler push 到 唯一数组 target[_observer_]
  target.observe = function(cb) {
    this[_observer_].push(cb);
  };

  // 用Proxy做代理
  return new Proxy(target, {
    set(target, property, value, receiver) {
      // 用Reflect 将操作转发给对象
      let success = Reflect.set(...arguments);
      if (success) {
        // 调用所有 handler
        target[_observer_].forEach(cb => cb(property, value));
      }
      return success;
    }
  });
}

var ob = makeObservable({})
ob.observe((k, v) => {
    console.log(`key:${k}, updated ${v}`)
})
ob.a = 233

ouput:
key:a, updated 233
233

ok,sir 本篇文章就是一个简单的记录文章,阐述了 观察者和发布订阅模式之前比较 浅显的区别,耦合和调度 不同。(应该说了好多遍,应该懂了,是的-。-), 另外记录了 发布订阅(vuebus) 的简单实现, proxy + reflect实现 类似mobx的 makeObservable. Have a Good Day