First, qualitative difference

First, the observer is one of the classic software design patterns, but publish-subscribe is just a messaging paradigm in software architecture. So don’t be misled by the “observer versus publish/subscribe model XXX” question.

Two, composition difference

Second, there is a clear difference in the number of roles needed to implement them. The observer mode itself requires only two characters, the observer and the observed, of which the observed is the key. Publish and subscribe requires at least three roles, including publisher, subscriber and publish and subscribe center, among which the publish and subscribe center is the key.

Observer model Release subscription
Two roles Three roles
The focus is on the observed The focus is on publishing subscription centers

Three, respective realization

1. Observer mode implementation

The observer mode generally has at least one observable object Subject, which can be observed by multiple observers. The relationship between the two is established by the observer actively, and the observer should have at least three methods — adding observer, removing observer, and notifying observer.

When the observed adds an observer to their list of observers, the relationship between the observer and the observed is established. The observer can then receive messages from the observed whenever the observed triggers the notification observer method at some point.

The figure above highlights the action of being notified by the observer, omitting how the observer is added to the list of being and how the notification message is received after being received by the observer. Let’s implement a version using JavaScript to illustrate the details.

1) Observed object

class Subject {

  constructor() {
    this.observerList = [];
  }

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

  removeObserver(observer) {
    const index = this.observerList.findIndex(o= > o.name === observer.name);
    this.observerList.splice(index, 1);
  }

  notifyObservers(message) {
    const observers = this.observeList;
    observers.forEach(observer= >observer.notified(message)); }}Copy the code

2) Observers

class Observer {

  constructor(name, subject) {
    this.name = name;
    if (subject) {
      subject.addObserver(this); }}notified(message) {
    console.log(this.name, 'got message', message); }}Copy the code

3) use

const subject = new Subject();
const observerA = new Observer('observerA', subject);
const observerB = new Observer('observerB');
subject.addObserver(observerB);
subject.notifyObservers('Hello from subject');
subject.removeObserver(observerA);
subject.notifyObservers('Hello again');
Copy the code

See appendix for complete code

4) parsing

The above code implements the observer and observed logic respectively, where the two are related in two ways:

  1. The observer actively requests to be added to the observed list
  2. The observed actively adds the observer to the list

The former explicitly states that the observer object is added to the observed notification list when it is created, while the latter actively adds the observed object to the list after the observer creates the instance.

2. Publish and subscribe

In contrast to the observer pattern, the publish and subscribe core builds the entire system on a central basis. In which, publishers and subscribers do not communicate directly, but the messages that publishers will publish are managed by the center, and subscribers subscribe to the messages in the center according to their own conditions.

Let’s imagine a mail system where you subscribe to notifications from a website as a subscriber, where the mail system acts as a publishing and subscription center, and the publisher is the website to which you subscribe.

The link starts with your subscription, although before you subscribe, someone else may have subscribed to some site and receive updates from the site. Your subscription is in a certain you want to subscribe to the website to fill in your email, if this step is to email system as the center, so it is within the email record the site information, subsequent when website content updates, mail system can receive and send mail to you in time, inform you in order to achieve the purpose of the subscriber.

1) Demote to observer mode

Here is the mail system as the center, if the site as the center, then you are equivalent to an observer for the site, you want to observe every move of the site, that is, the update of the site content. The subscribe action itself becomes the observer list that you ask the site to add your email to. The published-subscription model degenerates into an observer model when the site notifies all observers, namely subscribers, when content is updated.

2) Upgrade to publish subscribe

It can be seen that there is a coupling between the website and users at this time, that is, in addition to maintaining its own functions, the website also needs to maintain the list of subscribers, and complete the notification work after the content is updated. So part of the relationship between the user and the site is maintained inside the site. If the site wants to take that out of the way, it naturally reverts to the publish-subscribe model, where a separate message center manages the relationship between publishers and subscribers and receives changes and notifications.

With this comparison, we can see why the observer model is distinguished from the publish-subscribe model, and the differences between them.

3) Correlation with observer pattern

But is publishing and subscribing really separate from the observer model? Is not

In fact, the publish-subscribe implementation uses the observer pattern internally. Let’s review the core of the observer pattern. The observer pattern consists of the observer and the observed, and the observed is the key. The association between the two can be that after the observer is created, the method of adding observer is called to actively establish a relationship with an observer, or the object to be observed is declared when the observer is created, that is, the observed. The relationship between observer and observed is one-to-many, that is, one observed can be observed by multiple observers.

Then the analysis of the publish and subscription model shows that the relationship between the subscriber and the publish and subscription center is similar to that between the observer and the observed. Note that this is only similar, because although both the subscriber and the observer are the consuming party, they expect to receive changes from the other party in real time.

But the difference is that the observer pattern observed need after each change in the binding type to trigger notifications to the observer, because it is by the pattern of the observer pattern to realize the core of which is similar to the mechanism of event processing system, observed the obligation for their response are given feedback to observers, This is why the observer pattern is loosely coupled, because the function of the observed is not pure, and it contains part of the logic of the relationship between the observer and itself.

The difference between publishing and subscription is that because publishers delegate notification rights to the publishing and subscription center, publishers are only concerned with their own publishing logic and do not communicate directly with the subscribers of their published content. The same is true for subscribers, who only care about registering the column they want to receive notification with the publication and subscription center and implementing their own logic after receiving notification, regardless of where the message comes from or who the publisher is. So publishers and subscribers are completely decoupled by the publication and subscription center.

With the emergence of the publishing and subscription center as the middle tier, communication management for both producers and consumers has become more manageable and scalable. Such as this can also be implemented by the observer pattern event mechanism, namely the message center after receiving the new news instant notification to all subscribers of this job, but at this time of the publisher’s relationship with news center for one-on-one, and message center and the subscriber for one-to-many, news center of the publisher of a layer of the agent.

4) Implementation of publishing and subscription

Publishing and Subscription Center
class PubSub {
  constructor() {
    this.messages = {};
    this.listeners = {};
  }
  publish(type, content) {
    const existContent = this.messages[type];
    if(! existContent) {this.messages[type] = [];
    }
    this.messages[type].push(content);
  }
  subscribe(type, cb) {
    const existListener = this.listeners[type];
    if(! existListener) {this.listeners[type] = [];
    }
    this.listeners[type].push(cb);
  }
  notify(type) {
    const messages = this.messages[type];
    const subscribers = this.listeners[type] || [];
    subscribers.forEach((cb, index) = >cb(messages[index])); }}Copy the code
The publisher
class Publisher {
  constructor(name, context) {
    this.name = name;
    this.context = context;
  }
  publish(type, content) {
    this.context.publish(type, content); }}Copy the code
The subscriber
class Subscriber {
  constructor(name, context) {
    this.name = name;
    this.context = context;
  }
  subscribe(type, cb) {
    this.context.subscribe(type, cb); }}Copy the code

5) use

const TYPE_A = 'music';
const TYPE_B = 'movie';
const TYPE_C = 'novel';

const pubsub = new PubSub();

const publisherA = new Publisher('publisherA', pubsub);
publisherA.publish(TYPE_A, 'we are young');
publisherA.publish(TYPE_B, 'the silicon valley');
const publisherB = new Publisher('publisherB', pubsub);
publisherB.publish(TYPE_A, 'stronger');
const publisherC = new Publisher('publisherC', pubsub);
publisherC.publish(TYPE_C, 'a brief history of time');

const subscriberA = new Subscriber('subscriberA', pubsub);
subscriberA.subscribe(TYPE_A, res= > {
  console.log('subscriberA received', res)
});
const subscriberB = new Subscriber('subscriberB', pubsub);
subscriberB.subscribe(TYPE_C, res= > {
  console.log('subscriberB received', res)
});
const subscriberC = new Subscriber('subscriberC', pubsub);
subscriberC.subscribe(TYPE_B, res= > {
  console.log('subscriberC received', res)
});

pubsub.notify(TYPE_A);
pubsub.notify(TYPE_B);
pubsub.notify(TYPE_C);
Copy the code

See appendix for complete code

6) parsing

The above three publishing and subscription center, publisher and subscriber have their own implementation, among which the publisher and subscriber implementation is relatively simple, only need to complete their own publishing and subscription tasks. The subscriber can do follow-up after receiving the message. The point is that both need to be associated with the same publishing and subscription center, otherwise the communication between the two will not be related.

The publishing action of the publisher and the subscription action of the subscriber are independent of each other, and there is no need to pay attention to each other. The message is distributed by the publishing and subscription center.

Four, practical application

In practical application, the implementation of the above two may be more complex, but also according to their own scenes for deformation, so there is no need to stick to the standard implementation of the two. Because most of the design patterns and technical models are just programming ideas summarized by predecessors based on experience, knowing that they may provide enlightening help to solve some complex problems, and then use this kind of ideas to solve the problems of specific scenes cleverly.

As for specific application examples, I can think of the following practices, welcome to supplement.

  • The EventEmiter module comes with Node.js
  • Vue. Js data responsive implementation

Other places where you can find words like Watch, watcher, observe, observer, listen, listener, dispatch, trigger, emit, on, event, eventbus, EventEmitter , most likely using the observer model or publish-subscribe idea. The next time you find one of these words, check out the source code implementation to see some of the clever details that other coders have used to implement the Observer pattern or publish and subscribe.

Five, the statement

This article is for the understanding of the individual learning observer model and the publishing and subscription process. Please point out any mistakes in time. At the same time, this article is the original content, if you need to reprint, please indicate the source.

Vi. Reference materials

  • Wikipedia – Design patterns
  • Wikipedia-observer mode
  • Wikipedia – Publish subscriptions

The appendix

observer-model.js

class Subject {

  constructor() {
    this.observerList = [];
  }

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

  removeObserver(observer) {
    const index = this.observerList.findIndex(o= > o.name === observer.name);
    this.observerList.splice(index, 1);
  }

  notifyObservers(message) {
    const observers = this.observerList;
    observers.forEach(observer= >observer.notified(message)); }}class Observer {

  constructor(name, subject) {
    this.name = name;
    if (subject) {
      subject.addObserver(this); }}notified(message) {
    console.log(this.name, 'got message', message); }}function main() {
  const subject = new Subject();
  const observerA = new Observer('observerA', subject);
  const observerB = new Observer('observerB', subject);
  subject.notifyObservers(`Hello from subject at The ${new Date()}`);
  console.log('-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --')

  setTimeout(() = > {
    const observerC = new Observer('observerC');
    const observerD = new Observer('observerD');
    subject.addObserver(observerC);
    subject.addObserver(observerD);
    subject.notifyObservers(`Hello from subject at The ${new Date()}`);
    console.log('-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --')},1000)

  setTimeout(() = > {
    subject.removeObserver(observerA);
    subject.notifyObservers(`Hello from subject at The ${new Date()}`);
    console.log('-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --')},2000)
}

main();

// observerA got message Hello from subject at Fri Jun 25 2021 16:25:09 GMT+0800 (GMT+08:00)
// observerB got message Hello from subject at Fri Jun 25 2021 16:25:09 GMT+0800 (GMT+08:00)
// ------------------------
// observerA got message Hello from subject at Fri Jun 25 2021 16:25:10 GMT+0800 (GMT+08:00)
// observerB got message Hello from subject at Fri Jun 25 2021 16:25:10 GMT+0800 (GMT+08:00)
// observerC got message Hello from subject at Fri Jun 25 2021 16:25:10 GMT+0800 (GMT+08:00)
// observerD got message Hello from subject at Fri Jun 25 2021 16:25:10 GMT+0800 (GMT+08:00)
// ------------------------
// observerB got message Hello from subject at Fri Jun 25 2021 16:25:11 GMT+0800 (GMT+08:00)
// observerC got message Hello from subject at Fri Jun 25 2021 16:25:11 GMT+0800 (GMT+08:00)
// observerD got message Hello from subject at Fri Jun 25 2021 16:25:11 GMT+0800 (GMT+08:00)
// ------------------------
Copy the code

pub-sub.js

class PubSub {
  constructor() {
    this.messages = {};
    this.listeners = {};
  }
  publish(type, content) {
    const existContent = this.messages[type];
    if(! existContent) {this.messages[type] = [];
    }
    this.messages[type].push(content);
  }
  subscribe(type, cb) {
    const existListener = this.listeners[type];
    if(! existListener) {this.listeners[type] = [];
    }
    this.listeners[type].push(cb);
  }
  notify(type) {
    const messages = this.messages[type];
    const subscribers = this.listeners[type] || [];
    subscribers.forEach((cb, index) = >cb(messages[index])); }}class Publisher {
  constructor(name, context) {
    this.name = name;
    this.context = context;
  }
  publish(type, content) {
    this.context.publish(type, content); }}class Subscriber {
  constructor(name, context) {
    this.name = name;
    this.context = context;
  }
  subscribe(type, cb) {
    this.context.subscribe(type, cb); }}function main() {
  const TYPE_A = 'music';
  const TYPE_B = 'movie';
  const TYPE_C = 'novel';

  const pubsub = new PubSub();

  const publisherA = new Publisher('publisherA', pubsub);
  publisherA.publish(TYPE_A, 'we are young');
  publisherA.publish(TYPE_B, 'the silicon valley');
  const publisherB = new Publisher('publisherB', pubsub);
  publisherB.publish(TYPE_A, 'stronger');
  const publisherC = new Publisher('publisherC', pubsub);
  publisherC.publish(TYPE_C, 'a brief history of time');

  const subscriberA = new Subscriber('subscriberA', pubsub);
  subscriberA.subscribe(TYPE_A, res= > {
    console.log('subscriberA received', res)
  });
  const subscriberB = new Subscriber('subscriberB', pubsub);
  subscriberB.subscribe(TYPE_C, res= > {
    console.log('subscriberB received', res)
  });
  const subscriberC = new Subscriber('subscriberC', pubsub);
  subscriberC.subscribe(TYPE_B, res= > {
    console.log('subscriberC received', res)
  });

  pubsub.notify(TYPE_A);
  pubsub.notify(TYPE_B);
  pubsub.notify(TYPE_C);
}

main();

// subscriberA received we are young
// subscriberC received the silicon valley
// subscriberB received a brief history of time
Copy the code