Analyze the Observer and Publish/Subscribe modes

As a quick note, the biggest difference between Observer and Publish/Subscribe is the scheduling center.

Observer mode

Xiao Ming likes xiao Mei next door very much. She is pretty and old, but she is a little bit shy and arrogant. She doesn’t want to say hello deliberately, but wants to know her “casually”, which makes her a little bit annoyed. When Xiao Ming was thinking about 🤔, he suddenly heard a sudden knock on the door. He was so annoyed that he wanted to go to Battle. When he opened the door, he found that it was Xiao Midea’s delivery. Xiao Ming wants Lao Zhang, the Courier, to give Mei a message every time he goes to deliver a package.

Let’s start with an easy delivery man

// Five star Courier
class DeliverGuy {
  delivery(who) {
    console.log(`The ${new Date().toLocaleString()}Sex, sex, sex${who}Delivery `)}}Copy the code
  • Xiao Ming: Hey, Lao Zhang, I have a small matter, please help me.
  • Lao Zhang: Abba abba abba
  • Xiaoming: Two bottles of happy water
  • Lao Zhang: Good, remember, ( ̄▽ ̄)و

So Old Zhang started a sideline, and adhering to the psychology of doing bigger and stronger, feel that the future can develop well attitude, broaden their own sideline

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

  log(str) {
    console.log(`The ${new Date().toLocaleString()} The ${this.name}:  ${str}`)}}class DeliverGuy extends Person {
  constructor(props) {
    super(props)
  }
  delivery(who) {
    // Run first to implement the logic of registration
    this.observerList.forEach(ob= > {
      this.log(`${ob.p.name}I'm going to give you${who}Delivery, day by day, hang up, do... `)
      ob.cb && ob.cb()
      this.log('I'll have to pay more next time. Go to the delivery first)})this.log('Shoop shoop here${who}Delivery `)
  }

  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('Lao zhang')
let XiaoMing = new XiaoMing('Ming')

LaoZhang.addExtraObserver(XiaoMing, XiaoMing.OutForXiaoMei)
LaoZhang.delivery('little beauty')
2022/1/12In the morning11:37:01Lao Zhang: Xiao Ming, Ma Liao, I have to deliver goods to Mei, day by day, hang up ao,do.2022/1/12In the morning11:37:01Xiao Ming: I'M Ready for beautiful Day now 2022/1/12 11:37:01 am Lao Zhang: You have to pay more next time -. - Go to the delivery first. 2022/1/12 11:37:01 am Lao Zhang: Sex sexCopy the code

The expanded business method addExtraObserver is a typical observer mode processing method. The observer registration method is registered in its own processing logic and coupled together.

Publish the subscriber model

The biggest difference between the publish subscriber mode and the observer mode is the scheduling difference. The publish subscription mode decouples the observer mode.

Let’s simply write a vue bus publish-subscribe class

/** * Vue on off emit event to do message center */
class VueBus {
  static ins;

  constructor() {
    // Event channel dispatch center
    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'.'-. -)
Copy the code

The above is the simple implementation of vueBus Events. In my opinion, there is an obvious difference between vueBus Events and observers, that is, publishers and subscribers do not care about each other as a whole, with their own independent logic, and subscribers are informed by the scheduling center. The following diagram

AnyWhy to sum up

coupling

Coupling. I personally feel that the biggest difference between the observer model and the publish-subscribe model is coupling, the attached logic that registers into the observer in the specific target object. Publishing subscribers do not. The two are completely decoupled.

The concept of observer comparison is explained by the fact that the target and observer are both base classes, the target maintains a set of methods for the observer, and the observer provides only interfaces. The specific observer and the specific target become their respective base classes, and then the specific observer registers itself with the specific target. After the specific target changes, notice that the specific target schedules the logic of the specific observer

For a quick tidbit, extract some of the visualized logic from the observer model

/** * Simple observer class */
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)}/** * notify the observer */
  notifyObersver() {
    console.log(this)
    for (let i = 0; i < this.countObserver(); i++) {
      const observer = this.getObserver(i);
      // We can improve the orientation of this
      observer()
    }
  }
}

/ / target
class Subject extends Observer {
  constructor() {
    super()}}var s = new Subject()
s.addObserver(function() {
  console.log(this)
  console.log(1)})Copy the code

MakeObserver in Mobx is also an observer mode. Here is a simple implementation using ES6 Proxy and Reflct + closure


// use symbol as a unique variable
let _observer_ = Symbol('[[observer]]');

function makeObservable(target) {
  // Initialize target's own _observer_ list
  target[_observer_] = [];

  // Push handler to a unique array target[_observer_]
  target.observe = function(cb) {
    this[_observer_].push(cb);
  };

  // use Proxy as Proxy
  return new Proxy(target, {
    set(target, property, value, receiver) {
      // Use Reflect to forward the action to the object
      let success = Reflect.set(... arguments);if (success) {
        // Call all handlers
        target[_observer_].forEach(cb= > cb(property, value));
      }
      returnsuccess; }}); }var ob = makeObservable({})
ob.observe((k, v) = > {
    console.log(`key:${k}, updated ${v}`)
})
ob.a = 233

ouput:
key:a, updated 233
233
Copy the code

Ok, Sir This article is a simple document that illustrates the more superficial differences between observer and publish-subscribe patterns, coupling and scheduling. (Should have said many times, should understand, yes -. -), and a simple implementation of vuebus. Proxy + Reflect implements a makeObservable like Mobx. Have a Good Day