Preface: I didn’t understand the publish-subscribe model thoroughly before, I felt it was very similar to the observer model, but I saw some articles saying that the observer model is publish-subscribe model, which made a big deal. This article with personal understanding of the publish-subscribe model for a comb, if there are mistakes or deficiencies, I hope you don’t mind pointing out, common progress!!

In order to facilitate the understanding of the subscription model, I have written an article explained by the DYNAMIC diagram demonstration, which is more vivid and straightforward. The subscription and publishing code has also been modified to support the observer mode: “This time, Thoroughly understand the subscription and publishing model”, interested students can read.

Start with the name

The name provides two key information words: publish and subscribe, which are two actions and belong to two objects: publisher and subscriber. We can use daily cases to analyze these two behaviors and objects. For example, as users, we subscribe to douyu’s live game, and subscribe to King of Glory and League of Legends.

When each game has a new match, Douyu will notify the corresponding subscribers, so we are the subscribers and Douyu is the publisher. Users can subscribe to the games they are interested in, and Douyu can also publish different game messages. Users who receive the message notification can choose whether to watch the live broadcast or cancel the subscription.

So from a responsibility point of view, subscribers need to be able to subscribe (including unsubscribe) and publishers need to be able to publish messages. If the publisher publishes a message that is not subscribed by the subscriber, the subscriber does not need to care. For example, douyu notifies the audience of a new league of Legends event, and the audience subscribing to Honor of Kings will not receive this message.

A subscriber can subscribe to multiple events at the same time, of course, such as subscription hero has also subscribe to the king of glory, both subscribers can also subscribe to other publishers, for example, we can also subscribe to the nuggets, some users like front knowledge, some users like the back-end knowledge, can also subscribe to the weibo, etc., that is to say, a user can subscribe to the event at the same time there are a lot of.

Build a subscriber

A subscriber class can be constructed by starting with the subscriber class. A subscriber class can be constructed by starting with the subscriber class.

// Subscriber constructor
class Subscribe {
  constructor(name = 'subscriber') {
    this.name = name
    // Random ID simulation is unique
    this.id = Date.now() + Math.ceil(Math.random() * 10000)}listen({
    publisher,//Which publisher message is subscribed to,//Subscribing message handler//Handling method after receiving message}) {
    // Subscribe message callback function
    if (publisher instanceof Publish) {
      // A subscriber can subscribe to multiple publishers at the same time, so the callback concatenates the id of the corresponding publisher
      this[message + '_' + publisher.id + "_handler"] = handler
      publisher.addListener(this, message)
    }
    return this
  }
  unlisten(publisher, message) {
    if (publisher instanceof Publish) {
      publisher.removeListener(this, message)
    }
    return this}}Copy the code

The Subscribe code is simple. There are two methods listen and unlisten, which are used to Subscribe and unsubscribe, respectively. The object to be passed to Subscribe, publisher, and the message to Subscribe and the handler function to receive the message notification. Don’t worry about publisher.addListener until you create a Publish class.

To unsubscribe, you pass in the subscribed object and the subscribed message. Here, when passing in the message parameter, you only unsubscribe the message. If you do not pass the message parameter, you unsubscribe all the messages of the subscriber.

Both listen and unlisten return this, so that a subscriber instance can execute a sequential subscribe or unsubscribe method in chained fashion, as can the publish method in the following publisher class.

Create a publisher

First look at the code:

// Publisher constructor
class Publish {
  constructor(name = 'publisher') {
    this.messageMap = {} // Message event subscriber collection object
    // Random ID simulation is unique
    this.id = Date.now() + Math.ceil(Math.random() * 10000)
    this.name = name
  }

  addListener(subscriber, message) { // Add message subscribers
    if(! subscriber || ! message)return false

    if (!this.messageMap[message]) { // If the message list does not exist, create a new one
      this.messageMap[message] = []
    }

    const existIndex = this.messageMap[message].findIndex(exitSubscriber= > exitSubscriber.id === subscriber.id)

    if (existIndex === -1) {// added when the subscriber does not exist
      this.messageMap[message].push(subscriber)
    } else {// Update the callback handler when the subscriber exists
      let handlerKey = message + "_" + this.id + "_handler";
      this.messageMap[message][existIndex] = subscriber; }}removeListener(subscriber, message) { // Delete message subscribers
    if(! subscriber)return false

    // If message is sent, only the subscriptions under this message will be deleted; otherwise, all subscriptions under this subscriber will be deleted
    const messages = message ? [message] : Object.keys(this.messageMap)

    messages.forEach(_message= > {
      const subscribers = this.messageMap[_message];

      if(! subscribers)return false;

      let i = subscribers.length;
      while (i--) {
        if (subscribers[i].id === subscriber.id) {
          subscribers.splice(i, 1)}}if(! subscribers.length)delete this.messageMap[_message]
    })
  }

  publish(message, info) { // Issue a notification
    const subscribers = this.messageMap[message] || []

    let handlerKey = message + "_" + this.id + "_handler";
    subscribers.forEach(subscriber= > {
      subscriber[handlerKey](subscriber, info)
    })

    return this}}Copy the code

The main function of the publisher is not complicated, that is, when the subscriber subscribes to the message, addListener is executed, and the subscriber is stored in its own messageMap. The stored rule takes the subscribed message as the key, and the storage structure is as follows:

messageMap = {
    message1:[subscriber1,subscriber2,subscriber3,...] .message2:[subscriber1,subscriber2,subscriber3,...] .message3:[subscriber1,subscriber2,subscriber3,...] . }Copy the code

When a publisher publishes a message, it iterates through the observer list and executes its own callback handler. When a publisher adds a subscriber to addListener, it makes two decisions:

1. If a message is subscribed for the first time, create a subscriber list with that message as the key;

If a subscriber subscribes to a message multiple times, it updates its callback function with the last callback as the final callback.

When removeListener is passed in, if only one subscriber is passed in, the publisher removes all subscriptions for that subscriber’s messages from the publisher. If certain messages are passed in, the publisher removes only subscriptions for that subscriber’s messages. When all subscribers under a publisher message unsubscribe, the publisher’s store relationship is deleted and the empty array is reduced.

4. Instantiate test cases

Directly on the code:

Instantiate the publishers Juejin and Douyu
const juejin = new Publish('juejin')
const douyu = new Publish('douyu')

// instantiate subscriber 'programmer A' and 'programmer B'
const programmerA = new Subscribe('programmerA')
const programmerB = new Subscribe('programmerB')

// Subscribers subscribe to messages
// Programmer A subscribed to Juejin's Javascript and would be interested if it was about closure.
// I also subscribe to Juejin's Java, if the price of the content pushed is more than 15, I can't afford it
programmerA.listen({
	publisher: juejin,
	message: 'JavaScript'.handler: (self, info) = > {
		let { title, duration, price } = info

		let result = `title[${title}] - >${self.name} is not interested in it.`
		if(title === 'closure') {
			result = `title[${title}] - >${self.name} is interested in it.`
		}
		console.log(`receive the message JavaScript from ${juejin.name}: `, result)
	}
}).listen({
	publisher: juejin,
	message: 'Java'.handler: (self, info) = > {
		let { title, duration, price } = info
		let result = `price[${price}] :${self.name} can not afford it.`
		if(price <= 15) {
			result = `price[${price}] :${self.name} can afford it.`
		}
		console.log(`receive the message JavaScript from ${juejin.name}: `, result)
	}
})

// Programmer B subscribes to Douyu's League of Legends and likes it
// Also subscribe douyu king of Glory, also like it
// Also subscribe to Juejin JavaScript, the price is less than 10, can afford
programmerB.listen({
	publisher: douyu,
	message: League of Legends.handler: (self, info) = > {
		let { title } = info
		let result = `title[${title}] - >${self.name} is interested in it.`
		console.log(Receive the message from the League of Heroes${douyu.name}: `, result)
	}
}).listen({
	publisher: juejin,
	message: Honor of Kings.handler: (self, info) = > {
		let { title } = info
		let result = `title[${title}] - >${self.name} is interested in it.`
		console.log(Receive the message from the League of Heroes${douyu.name}: `, result)
	}
}).listen({
	publisher: juejin,
	message: 'JavaScript'.handler: (self, info) = > {
		let { title, duration, price } = info
		let result = `price[${price}] :${self.name} can not afford it.`
		if(price <= 10) {
			result = `price[${price}] :${self.name} can afford it.`
		}
		console.log(`receive the message JavaScript from ${juejin.name}: `, result)
	}
})

// Juejin publishes a message notification
juejin.publish('JavaScript', {
	title: 'prototype'.duration: 20.price: 12
}).publish('JavaScript', {
	title: 'closure'.duration: 15.price: 8
}).publish('Java', {
	title: 'interface'.duration: 18.price: 10
})
// Douyu publishes a message notification
douyu.publish(League of Legends, {
	title: 'RNG VS SSW'.startTime: 'the 2019-09-01 16:00',
}).publish(Honor of Kings, {
	title: 'KPL league'.startTime: 'the manner of 2019-08-30',})// Programmer B unsubscribe from Douyu and learn
programmerB.unlisten(douyu)

// The publisher posts the message again
juejin.publish('JavaScript', {
	title: 'React'.duration: 20.price: 25
}).publish('JavaScript', {
	title: 'Vue'.duration: 15.price: 20
})

douyu.publish(League of Legends, {
	title: 'RNG VS SSW'.startTime: 'the 2019-09-02 16:00',
}).publish(Honor of Kings, {
	title: 'KPL league'.startTime: 'the manner of 2019-08-31',})Copy the code

Five, the summary

The publish-subscribe model is based on message connectivity, and execution results must occur if the subscriber and publisher are the same message. Subscribers focus only on the messages they subscribe to, and each subscriber can subscribe to more than one publisher object at a time. When publishing a message, each publisher does not care whether the message has a subscriber or not. When a publisher publishes a message subscribed by a subscriber, the subscriber will act accordingly according to the message details.

What the subscriber does after receiving the message is no longer relevant to the publisher; the callback logic is completely decoupled from the publishing logic. Subscribers can also subscribe to the objects and messages they are interested in, and the publish-subscribe model has a clear subject-verb relationship in logic.