After reading an article about the difference between the observer mode and the subscription publishing mode, I still think they are the same in concept and thought, but they are called different according to the different implementation methods and use scenarios. However, since there are differences, I will explore them and deepen my understanding.

First look at the picture to feel the difference between the two:

Two model concepts

The observer pattern is defined as a one-to-many dependency between objects that automatically notifies interested observers when the state of the object itself changes.

The coupling of functions between the subject object and the observer is resolved, that is, the change of state of one object notifies other objects.

This kind of object-to-object relationship is a bit like the business-to-customer relationship. If the customer is interested in a product of the merchant, it will be remembered by the merchant. When a new product is released, it will directly inform the customer.

Here’s a picture:

As can be seen from the picture, this model is that the merchant directly manages the customer.

Subscription publishing model

This pattern, like the observer pattern, defines one-to-many dependencies and notifies all interested subscribers of changes in the object’s state.

Subscription publishing mode has the action of subscription, can not directly contact merchants, as long as it can subscribe to the concerned state, usually using third-party media to do, and publishers will also use third-party media to notify subscribers.

This is a bit like the relationship between merchants and app-customers. When a product is out of stock, customers can subscribe to the notification on the APP. When new products are available, the merchants will notify the customers who subscribe through the APP.

In program implementation, the third-party media is called EventBus(EventBus), which can be understood as a collection of subscription events. It provides subscribing, publishing, and canceling functions. Subscribers subscribe to events, and publishers publish events, and both interact through the event bus.

Similarities and differences between the two models

Conceptually, the two are no different, addressing decoupling between objects by triggering events at a point in time that subscribers listening for can act on.

Different in implementation, the observer mode is maintained by the publisher itself for subscribers to subscribe to events, and some subsequent operations are completed by the publisher. In the subscription publishing model, there is an event bus between the subscriber and the publisher, through which all operations are completed.

The name of the event in observer mode, usually specified by the publisher to publish the event, can also be customized to see if custom functionality is provided.

Bind events in the DOM, such as click and mouseover, with built-in event names.

document.addEventListener('click'.() = >{})
Copy the code

The first argument to addEventListener is the binding time name; The second argument is a function, which is the subscriber.

The subscription publishing mode has an arbitrary event name and maintains a list of subscribers for an event in the event bus. When the event is triggered, the list is traversed to notify all subscribers.

Pseudo code:

/ / subscribe
EventBus.on('custom'.() = > {})
/ / release
EventBus.emit('custom')
Copy the code

Event names are user-defined by the developer and can be difficult to maintain when they are frequently used. In particular, event names need to be changed and multiple objects or components need to be replaced. Event names are generally managed in a single configuration.

Code implementation

Observer model

In Javascript functions are objects, and the subscriber object can be directly represented by a function, just like the addEventListener method used to bind DOM. The second argument is the subscriber, which is a function.

We implement the business-to-customer approach from the concept described above, so that it can be better understood (or confused).

To define a customer class, there needs to be a method, which is used to receive messages notified by merchants. Just like customers have their mobile phone numbers, the published messages are received by mobile phones, and customers receive messages in a unified way.

/ / the customer
class Customer {
  update(data){
    console.log('Got the data', data); }}Copy the code

Define merchants that provide subscribing, unsubscribing, and publishing capabilities

/ / the businessman
class Merchant {
  constructor(){
    this.listeners = {}
  }
  addListener(name, listener){
    // The event does not define a queue
    if(this.listeners[name] === undefined) {
      this.listeners[name] = []
    }
    // Put it in the queue
    this.listeners[name].push(listener)
  }
  removeListener(name, listener){
    // If the event has no queue, it is not processed
    if(this.listeners[name] === undefined) return
    // Walk through the queue to find the function to remove
    const listeners = this.listeners[name]
    for(let i = 0; i < listeners.length; i++){
      if(listeners[i] === listener){
        listeners.splice(i, 1)
        i--
      }
    }
  }
  notifyListener(name, data){
    // If the event has no queue, it is not processed
    if(this.listeners[name] === undefined) return
    // Traverse the queue, executing the functions in turn
    const listeners = this.listeners[name]
    for(let i = 0; i < listeners.length; i++){
      if(typeof listeners[i] === 'object'){
        listeners[i].update(data)
      }
    }
  }
}
Copy the code

Use it:

// Several customers
const c1 = new Customer()
const c2 = new Customer()
const c3 = new Customer()

/ / the businessman
const m = new Merchant()

// The customer subscribes to the merchant's merchandise
m.addListener('shoes', c1)
m.addListener('shoes', c2)
m.addListener('skirt', c3)

// Unsubscribe after a day
setTimeout(() = > {
  m.removeListener('shoes', c2)
}, 1000)

// A few days later
setTimeout(() = > {
  m.notifyListener('shoes'.'Come on, buy it.')
  m.notifyListener('skirt'.'Reduced price')},2000)
Copy the code

Subscription publishing model

The subscription and publishing capabilities are in the event bus.

class Observe {
  constructor(){
    this.listeners = {}
  }
  on(name, fn){
    // The event does not define a queue
    if(this.listeners[name] === undefined) {
      this.listeners[name] = []
    }
    // Put it in the queue
    this.listeners[name].push(fn)
  }
  off(name, fn){
    // If the event has no queue, it is not processed
    if(this.listeners[name] === undefined) return
    // Walk through the queue to find the function to remove
    const listeners = this.listeners[name]
    for(let i = 0; i < this.listeners.length; i++){
      if(this.listeners[i] === fn){
        this.listeners.splice(i, 1)
        i--
      }
    }
  }
  emit(name, data){
    // If the event has no queue, it is not processed
    if(this.listeners[name] === undefined) return
    // Traverse the queue, executing the functions in turn
    const listenersEvent = this.listeners[name]
    for(let i = 0; i < listenersEvent.length; i++){
      if(typeof listenersEvent[i] === 'function'){
        listenersEvent[i](data)
      }
    }
  }
}
Copy the code

Use:

const observe = new Observe()

// Subscribe
observe.on('say'.(data) = > {
  console.log('Listen, get the data.', data);
})
observe.on('say'.(data) = > {
  console.log('Listen 2, get the data', data);
})

/ / release
setTimeout(() = > {
  observe.emit('say'.'Sent the data.')},2000)
Copy the code

From the perspective of the implementation of the above two patterns, the observer pattern is further abstracted, and the common code can be extracted as the event bus. Conversely, if an object wants to have the functions of the observer pattern, it only needs to inherit the event bus.

Node provides the Events module for us to use flexibly.

Inherited use, both called by the publisher:

const EventEmitter = require('events')

class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter()

myEmitter.on('event'.(data) = > {
  console.log('Trigger event', data);
});
myEmitter.emit('event'.1);
Copy the code

Used directly as an event bus:

const EventEmitter = require('events')

const emitter = new EventEmitter()

emitter.on('custom'.(data) = > {
  console.log('Receive data', data);
})

emitter.emit('custom'.2)
Copy the code

Application scenarios

The observer pattern is used in a number of scenarios. In addition to listening for events on the DOM as described above, the most common is parent-child communication in Vue components.

Parent code:

<template>
  <div>
    <h2>The parent</h2>
    <Child @custom="customHandler"></Child>
  </div>
</template>
<script>
  export default {
    methods: {
      customHandler(data){
        console.log('Get the data. I'm gonna do something.', data); }}}</script>
Copy the code

Sub-code:

<template>
  <div>
    <h2>The child</h2>
    <button @click="clickHandler">Changed the</button>
  </div>
</template>
<script>
  export default {
    methods: {
      clickHandler(){
        this.$emit('custome'.123)}}}</script>
Copy the code

The sub-component is a generic component that does no internal business logic processing and only publishes a custom event custom when clicked. The child component is used anywhere on the page, in different usage scenarios, when the button is clicked, the scene in which the child component is located will do the corresponding business processing. If you are interested in changing the state of a button click inside a child component, just listen for custom custom events.

The subscription and publication mode can also be used in Vue writing services. The application scenario is that when communicating across multiple layers of components, if the parent and child components are used to communicate layer by layer and subscribe and publish, the maintainability and flexibility are very poor. Once something goes wrong in the middle, the whole communication link will break down. In this case, a separate EventBus is used to solve these problems, which allows you to subscribe to and publish events as long as you have access to the EventBus object.

// EventBus.js
import Vue from 'vue'
export default const EventBus = new Vue()
Copy the code

Parent code:

<template>
  <div>
    <h2>The parent</h2>
    <Child></Child>
  </div>
</template>
<script>
  import EventBus from './EventBus'
  export default {
    // Monitor the load
    moutend(){
      EventBus.on('custom'.(data) = > {
        console.log('Get the data', data); }}})</script>
Copy the code
<template>
  <div>
    <h2>Nested deep children</h2>
    <button @click="clickHandler">Changed the</button>
  </div>
</template>
<script>
  import EventBus from './EventBus'
  export default {
    methods: {
      clickHandler(){
        EventBus.emit('custom'.123)}}}</script>
Copy the code

As you can see from the code above, the subscribe publish pattern completely decouples the two components, allowing them to subscribe or publish custom events at the right time without knowing about each other.

Use of subscription publishing model in Vue2 source code

Vue2 will collect dependent data through the acquisition of interception data, and collect Watcher by Watcher. While waiting for changes to the data, notify the dependent Watcher for component updates. This collection and notification process can be seen in a diagram.

These dependencies exist in the defined Dep, which implements simple subscribe and publish functions in this class, which can be viewed as an EventBus, source code:

export default class Dep {
  statictarget: ? Watcher; id: number; subs:Array<Watcher>;

  constructor () {
    this.id = uid++
    this.subs = []
  }

  addSub (sub: Watcher) {
    this.subs.push(sub)
  }

  removeSub (sub: Watcher) {
    remove(this.subs, sub)
  }

  depend () {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }

  notify () {
    // stabilize the subscriber list first
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}
Copy the code

Each Wather is a subscriber, and each of these subscribers implements a method called update that iterates through all Wather calls to update when data changes.

conclusion

The observer pattern and the subscribe publish pattern are the same. The concept and the problem they solve are the same. They aim to decouple two objects, but they are called different. It can also be said that they are not the same, in the use of the way and the scene is not the same.

If it helps you, please pay attention to unlock front-end skills.