Interviewer: Can you write a publishing subscription model with JS?

What is a publish subscription model? Can you do this by handwriting? Is it different from the observer mode?...

1 Scene introduction

Let's start with a scenario like this:

Suppose you have a social platform with a V called Nami

Nami is brilliant and versatile. Currently she has two skills: she can write songs and take videos.

She will publish these works on the platform. Fans who follow her receive this content

Now he has three fans: Luffy, Zoro, Sanji

Every time Nami publishes a work, the messages received on the accounts of three fans are updated

Now in code:

const luffy = {
  update: function (songs, videos) {
    console.log(songs, videos);
  },
};
const zoro = {
  update: function (songs, videos) {
    console.log(songs, videos);
  },
};
const sanji = {
  update: function (songs, videos) {
    console.log(songs, videos);
  },
};

const nami = {
  // This method will be called whenever Nami's work is updated
  workUpdate: function () {
    // Getting Works
    const songs = this.getSongs();
    const videos = this.getVideos();

    // Account Update
    luffy.update(songs, videos);
    zoro.update(songs, videos);
    sanji.update(songs, videos);
  },
  getSongs: function () {
    return "mp3";
  },
  getVideos: function () {
    return "mp4";
  },
};

Now the problem is

  1. If Nami harvested another fan, Robin, I would add a robin object and modify the workUpdate method
  2. If Nami has a new skill: Writing a novel, I will modify both the workUpdate function and the update method in each fan object, because the parameter adds a new one

Did you find any problems?

The coupling between fans and big V objects is too high, making it difficult for them to expand independently

2 Code optimization

2.1 Solve the problem of adding fans

Fix the first problem above so that you don't have to modify the workUpdate method when adding fans

First, we abstract Big V into a Star class, save the list of fans with array fans, and add a new way to add fans

class Star {
  constructor() {
    this.fans = [];
  }
  addFans(fan) {
    this.fans.push(fan)
  }
  workUpdate() {
    const songs = this.getSongs();
    const videos = this.getVideos();
    this.fans.forEach((item) => item.update(songs, videos));
  }
  getSongs() {
    return "MP3";
  }
  getVideos() {
    return "MP4";
  }
}

Then, we abstract "fans" into a Fan-like object. When we create a fan object, we pass in a "big V" object and call the big V's addFans method to add to the fan list.

class Fan {
  constructor(name, star) {
    this.name = name
    this.star = star
    this.star.addFans(this)
  }
  update(songs, videos) {
    console.log(songs, videos);
  }
}

Now that we add fans, we don't have to change the code anymore

const nami = new Star()
const luffy = new Fan("luffy", nami);
const zoro = new Fan("zoro", nami);
const sanji = new Fan("sanji", nami);
const robin = new Fan("robin", nami);
nami.workUpdate()

2.2 Solving the problem of adding works

We added an array of works to hold the work of Big V and add get and set methods to it

class Star {
  constructor() {
    this.fans = [];
    this.works = [];
  }
  addFans(fan) {
    this.fans.push(fan);
  }
  setWorks(work) {
    this.works.push(work);
    // After adding a work, call the update method
    this.workUpdate();
  }
  getWorks() {
    return this.works;
  }
  workUpdate() {
    this.fans.forEach((item) => item.update());
  }
}

Modify the class Fan accordingly:

class Fan {
  constructor(name, star) {
    this.name = name
    this.star = star
    this.star.addFans(this)
  }
  update() {
    console.log(`${this.name}:${this.star.getWorks()}`)
  }
}

Now you don't have to change the code when Big V adds your work:

const nami = new Star();
nami.setWorks('song')
nami.setWorks('video')
nami.setWorks('novel')
const luffy = new Fan("luffy", nami);
const zoro = new Fan("zoro", nami);
const sanji = new Fan("sanji", nami);
nami.workUpdate();

3 Observer mode

You can see that in the above example, there is a one-to-many dependency between a nami object and multiple fan objects, and when a nami object has a work update, all those who care about her will be notified.

In fact, this is the observer model

Observer mode: Defines a one-to-many dependency between objects, in which all dependent objects are notified and automatically updated when the state of an object changes

We will further abstract the code in 2.2:

View "fans" as observers and "big V" as objects to be observed, called Subjects

Subject maintains an observer list (the original fans array). When the status of a Subject changes (the original work is updated), notify all observers by calling the notify (original workUpdate) method and execute their update method

The code is as follows:

// Observed: Subject
class Subject {
  constructor() {
    this.observerList = [];
    // Represents the subject state
    this.state = 0;
  }
  addObserver(observer) {
    this.observerList.push(observer);
  }
  // Change Theme Status
  setState(state) {
    this.state = state;
    // Notify all observers when state changes
    this.notify();
  }
  getState() {
    return this.state;
  }
  notify() {
    this.observerList.forEach((observer) => observer.update());
  }
}

// Observer
class Observer {
  constructor(name, subject) {
    this.name = name;
    this.subject = subject;
    this.subject.addObserver(this);
  }
  update() {
    console.log(`${this.name}:${this.subject.state}`);
  }
}

4 Broker on stage

Because Big V is busy, they need a broker to keep the artists connected with their fans

The broker's work includes:

  1. Maintain big V fans, broker will have a list of fans
  2. Big V's new work is handed over to the broker, who is responsible for sending the new work to the fans on the fan list

Abstract into a class as follows:

class Manager {
  constructor() {
    this.fans = [];
    this.works = [];
  }
  addFans(fan) {
    this.fans.push(fan);
  }
  setWorks(work) {
    this.works.push(work);
    // After adding a work, call the update method
    this.workUpdate();
  }
  getWorks() {
    return this.works;
  }
  workUpdate() {
    this.fans.forEach((item) => item.update());
  }
}

Hmm? Where does this code seem to have been seen?

Yes, it's the same as the Star class in 2.2, just changing the class name.

Does that make sense?

In fact, the code is the same because in the Star class of 2.2 we only wrote about publishing (i.e., publishing works) and subscribing (i.e., maintaining fan lists); The Star class itself may do more than that, for example, authoring content.

Now we take the publishing and subscription work out of the Star class and leave Manager solely responsible. The Star class simply gives the work to Manager after the creation is complete

Fan, on the other hand, no longer interacts directly with Star. Fan only cares about receiving the work, so Fan interacts directly with Manger, Fan subscribes (which acts like adding fans to the list of fans maintained by Manger) Manager and gets the work he wants from Manager

So the code for Star and Fan is as follows:

class Star {
  constructor() {}
  // A literary creation
  create(manager) {
    // Deliver the created new work to the broker
    manager.setWorks("new work");
  }
}

class Fan {
  constructor(name, manager) {
    this.name = name;
    this.manager = manager;
    this.manager.addFans(this);
  }
  update() {
    console.log(`${this.name}:${this.manager.getWorks()}`);
  }
}

5 Publish subscription mode

Previously, we used a broker to take care of publishing and subscribing without having Star and Fan interact directly to achieve a decoupling effect

This is the publish-subscribe mode

We will further Abstract Manger in 4:

Think of "fans" as Subscribers; Think of Big V as the publisher of content, which is called Publisher in publish subscription mode. Think of a broker as a publishing and subscription center (or Broker)

The code is as follows:

// Publish Subscription Dispatch Center
class Broker {
  constructor() {
    this.subscribers = [];
    // Represents the subject state
    this.state = 0;
  }
  // Subscribe
  subscribe(subscriber) {
    this.subscribers.push(subscriber);
  }
  // Change Theme Status
  setState(state) {
    this.state = state;
    // Publish after status changes
    this.publish();
  }
  getState() {
    return this.state;
  }
  // Release
  publish() {
    this.subscribers.forEach((subscriber) => subscriber.update());
  }
}

// Publisher
class Publisher {
  constructor() {}
  changeState(broker, state) {
    broker.setState(state);
  }
}

class Subscriber {
  constructor(name, broker) {
    this.name = name;
    this.broker = broker;
    this.broker.subscribe(this);
  }
  update() {
    console.log(`${this.name}:${this.broker.getState()}`);
  }
}

Let's run it and see what happens:

// Create Dispatch Center
const broker = new Broker()
// Create Publisher
const publisher = new Publisher()
// Create Subscriber
const subscribe1 = new Subscriber('s1', broker)
const subscribe2 = new Subscriber('s2', broker)
const subscribe3 = new Subscriber('s3', broker)
// The publisher changes state and notifies the dispatch center, which notifies subscribers
publisher.changeState(broker, 1)

6 Comparing the Observer Mode with the Publish Subscription Mode

From the number of roles

  • The observer model has only two roles: the observer and the observer
  • The Publish Subscription Model has three roles: Publisher, Subscriber, and Intermediate (Publish Subscription Center)

Coupling

  • The observer mode is in a loosely coupled state, where the two are still interacting but can easily expand independently and not influence each other.
  • Publishers and subscribers in publish-subscribe mode do not have coupling at all, which achieves decoupling effect between objects

From an intentional point of view

  • Both: implements a one-to-many dependency between objects, and when the state of an object changes, all objects that depend on it are notified and automatically updated

Public Number [Front End]

Keywords: Javascript Front-end Design Pattern

Added by NSH on Tue, 02 Nov 2021 20:45:53 +0200