preface

In software engineering, design pattern is a solution proposed to various problems that are ubiquitous (recurring) in software design.

Design patterns are not used to write code directly, but rather to describe a solution to a problem in a variety of different situations.

Design patterns can transform the unstable to the relatively stable, the concrete to the relatively abstract, avoid the tight coupling that can cause trouble, and enhance the ability of software design to face and adapt to change

“– Wikipedia

Design pattern is a software development idea that helps reduce code coupling and enhance code robustness. It is often used in large projects.

Today I’m going to talk about the Observer pattern and the publish/subscribe pattern. This is very useful in decoupling.

What is the observer mode?

Here’s a simple example:

Before graduation, many students would say something like this:

“Lao Wang, when you get married, remember to ask me to drink the wedding wine!”

So one day you are really going to get married and you need to have a banquet, and you need to tell your old friends to come and have a wedding. So you pick up your cell phone and call all your best friends around the world about the wedding reception.

To the banquet that day, some friends came, some people did not come to the ceremony but arrived, some only a short two words of blessing, the rest of the only excuse.

This is the observer mode

In the observer mode, the object and the observer are independent but related:

  • Both are independent pairs of objects.
  • The observer subscribes to events in the target object, and the target broadcasts the publication event.

Just like in the previous example:

  • Lao Wang is the so-called target in the model.
  • What students say before graduation is the equivalent of subscribing to events on the target.
  • Lao Wang calls a friend to publish an event.
  • The students responded with different actions.

So our code is slowly building up.

First we need to define two objects:

  1. Target object: Subject
  2. Observer Object: Observer

And the reference of the observer object should be stored in the target object, just like Lao Wang should be stored in the mobile phone of his classmates, only save can contact. So we have the following code:

function Subject() {
  this.observers = new ObserverList();
}
function ObserverList() {
  this.observerList = [];
}
function Observer() {}
Copy the code

References to target objects must be dynamically controlled:

ObserverList.prototype.add = function(obj) {
  return this.observerList.push(obj);
};

ObserverList.prototype.count = function() {
  return this.observerList.length;
};

ObserverList.prototype.get = function(index) {
  if (index > - 1 && index < this.observerList.length) {
    return this.observerList[index]; }}; ObserverList.prototype.indexOf =function(obj, startIndex) {
  var i = startIndex;

  while (i < this.observerList.length) {
    if (this.observerList[i] === obj) {
      return i;
    }
    i++;
  }
  return - 1;
};

ObserverList.prototype.removeAt = function(index) {
  this.observerList.splice(index, 1);
};

Subject.prototype.addObserver = function(observer) {
  this.observers.add(observer);
};

Subject.prototype.removeObserver = function(observer) {
  this.observers.removeAt(this.observers.indexOf(observer, 0));
};
Copy the code

In this way, we can add, delete and check the contacts on Lao Wang’s mobile phone.

Now it’s time to consider the functionality for Posting messages. First, it must be made clear that the target object does not specify what changes the observer object will make accordingly. The target object has only notification functions. For example, Lao Wang can only tell his friends that he is going to have a wedding, but it is up to them to decide what to do next.

So we have to write a function that targets the broadcast message:

Subject.prototype.notify = function(context) {
  var observerCount = this.observers.count();
  for (var i = 0; i < observerCount; i++) {
    this.observers.get(i).update(context); }};Copy the code

We leave the changes to the specific observer object to the observer object itself. This requires that the Observer object should have its own update(context) method to make changes, and this method should not be written on the prototype chain, because each instantiated Observer responds differently and needs to store the update(context) method independently:

function Observer() {
  this.update = function() {
    // ...
  };
}
Copy the code

This completes the construction of a simple observer pattern.

Complete code:

function ObserverList() {
  this.observerList = [];
}

ObserverList.prototype.add = function(obj) {
  return this.observerList.push(obj);
};

ObserverList.prototype.count = function() {
  return this.observerList.length;
};

ObserverList.prototype.get = function(index) {
  if (index > - 1 && index < this.observerList.length) {
    return this.observerList[index]; }}; ObserverList.prototype.indexOf =function(obj, startIndex) {
  var i = startIndex;

  while (i < this.observerList.length) {
    if (this.observerList[i] === obj) {
      return i;
    }
    i++;
  }
  return - 1;
};

ObserverList.prototype.removeAt = function(index) {
  this.observerList.splice(index, 1);
};

function Subject() {
  this.observers = new ObserverList();
}

Subject.prototype.addObserver = function(observer) {
  this.observers.add(observer);
};

Subject.prototype.removeObserver = function(observer) {
  this.observers.removeAt(this.observers.indexOf(observer, 0));
};

Subject.prototype.notify = function(context) {
  var observerCount = this.observers.count();
  for (var i = 0; i < observerCount; i++) {
    this.observers.get(i).update(context); }};// The Observer
function Observer() {
  this.update = function() {
    // ...
  };
}
Copy the code

What is the publish-subscribe model?

Here’s a simple example:

Our lives, especially for young people struggling in first-tier cities, cannot be more closely connected with renting. At the same time, there are many rental agencies around us.

One day a passer-by needs to rent a suite of three rooms, a living room, a kitchen and a bathroom, he found the intermediary asked whether there is. The intermediary looked and found that there was no room type he wanted, so he said to a passer-by: “Wait for the landlord to provide this type of room when I will contact you.” So you went back to wait for news.

One day, a landlord sent his redundant housing information and pictures to the intermediary, the intermediary looked at it, it is not the passer-by a to the room. So immediately call to give way to a house. I finally made a deal.

This is the publish-subscribe model

As you can see, the most important Topic/Event Channel (Event) object in the publish-subscribe pattern. We can simply call it “mediation.”

In this mediation object, the message is both accepted by the publisher and sent to the subscribers. So the mediation should also store subscriber information for different events.

We first give each subscriber object of the mediation object an identity, and we give a subUid every time a new subscriber subscribing to the event.

Let’s write the mediation object (pubSub) :

var pubsub = {};
(function(myObject) {
  var topics = {};
  var subUid = - 1;

  myObject.publish = function() {};

  myObject.subscribe = function() {};

  myObject.unsubscribe = function() {};
})(pubsub);
Copy the code

Here we use the factory pattern to create our mediation object.

Let’s implement the subscription function first:

The first thing we need to realize is that topics objects will store the following types of data:

topics = {
  topicA: [{token: subuid,
      function: func}... ] .topicB: [{token: subuid,
      function: func}... ] . }Copy the code

For topics objects, store many different event names (topicA…) For each event, there is an array object specified to hold the subscribed object and the response after the event occurs.

So when you have a subscription object subscribing to an event in a mediation:

myObject.subscribe = function(topic, func) {
  // Create an event if one does not exist
  if(! topics[topic]) { topics[topic] = []; }// Record the subscription object information
  var token = (++subUid).toString();
  topics[topic].push({
    token: token,
    func: func
  });
  // Return the subscriber id, which is used when unsubscribing
  return token;
};
Copy the code

Let’s implement the unsubscribe function:

We just need to iterate over the objects in topics for each event.

myObject.unsubscribe = function(token) {
  for (var m in topics) {
    if (topics[m]) {
      for (var i = 0, j = topics[m].length; i < j; i++) {
        if (topics[m][i].token === token) {
          topics[m].splice(i, 1);
          returntoken; }}}}return this;
};
Copy the code

All that remains is the implementation of the release event:

We just need to give the event name topic and the corresponding parameters, find the list of subscribers corresponding to the event, and iterate over the methods in the call list.

myObject.publish = function(topic, args) {
  if(! topics[topic]) {return false;
  }
  var subscribers = topics[topic],
    len = subscribers ? subscribers.length : 0;
  while (len--) {
    subscribers[len].func(args);
  }
  return this;
};
Copy the code

At this point, our mediation object is complete. In a publish-subscribe model we don’t care about publishers and subscribers.

Complete code:

var pubsub = {};

(function(myObject) {
  var topics = {};
  var subUid = - 1;

  myObject.publish = function(topic, args) {
    if(! topics[topic]) {return false;
    }
    var subscribers = topics[topic],
      len = subscribers ? subscribers.length : 0;
    while (len--) {
      subscribers[len].func(args);
    }
    return this;
  };

  myObject.subscribe = function(topic, func) {
    if(! topics[topic]) { topics[topic] = []; }var token = (++subUid).toString();
    topics[topic].push({
      token: token,
      func: func
    });
    return token;
  };

  myObject.unsubscribe = function(token) {
    for (var m in topics) {
      if (topics[m]) {
        for (var i = 0, j = topics[m].length; i < j; i++) {
          if (topics[m][i].token === token) {
            topics[m].splice(i, 1);
            returntoken; }}}}return this;
  };
})(pubsub);
Copy the code

The difference and connection between the two

The difference between:

  1. The observer pattern requires that the observer object itself define the corresponding method when the event occurs.
  2. The publish-subscribe pattern adds a mediation object between the publish object and the subscribe object. We don’t care about the internals of the publisher and subscriber objects, the details of the response time are all implemented by the mediation object.

Contact:

  1. Both reduce code coupling.
  2. Both have message passing mechanism and data-centric design idea.

In actual combat

A little bit of knowledge about template engines is needed here. You can read my previous post about template engines: JavaScript Template Engines at Hand.

Suppose we have the following template to render:

var template = `<span><% this.value %></span>`;
Copy the code

The template depends on the following data sources:

var data = {
  value: 0
};
Copy the code

Now if the value in data is dynamic, incrementing by 1 every second.

setInterval(function() {
  data.value++;
}, 1000);
Copy the code

At the same time we need to make changes on the page, you might write code like this:

setInterval(function() {
  data.value++;
  document.body.innerHTML = TemplateEngine(template, data);
}, 1000);
Copy the code

Consider the implementation of the publish-subscribe pattern:

var template = `<span><% this.value %></span>`;
var data = {
  value: 0
};
function render() {
  document.body.innerHTML = TemplateEngine(template, data);
}
window.onload = function() {
  render();
  pubsub.subscribe("change", render);
  setInterval(function() {
    data.value++;
    pubsub.publish("change");
  }, 1000);
};
Copy the code

The former may seem straightforward, but:

  1. The different features are tightly coupled, and if you want to change them later, it’s likely to change the whole thing.
  2. The actual development, where we have more than one subscriber and more than one publisher message, is far more complex than the logic in this example. Cut again, cut again.

The publish-subscribe model, by contrast, is logical, maintainable, and worth looking at.

Mention: the implementation of event listening

Event listening is a feature that we often use. In fact, it is implemented from the publish and subscribe model.

subject.addEventListener("click", () = > {/ /...
});
Copy the code

This is a call to subscribe to an event.

The observer model and the publish/subscribe model are very relevant to us! 😁

-EFO-


I have created a repository on Github to record the tips, difficulties, and pitfalls of learning full stack development. Please click on the link below to browse. If you feel good, please give a little star! 👍


2019/04/28

AJie