Publish-subscribe pattern (Observer pattern)

The publis-subscribe pattern, also known as the observer pattern, defines a one-to-many dependency between objects. When an object’s state changes, all dependent objects are notified.

  • In JavaScript development, we often use the event model as an alternative to the traditional publish-subscribe model.

Real life examples

Xiao Ming learned that CCTV5 will soon broadcast NBA games

So Xiaoming took down the phone number of CCTV5 and would call every day to ask when the NBA began. Besides Xiao Ming, xiao Hong, Xiao Qiang and Xiao Long also consult CCTV5 about this problem every day. After a week, the MM of CCTV5 decided to quit because he was tired of answering 1000 phone calls of the same content every day.

Of course, ccTV5 is not so stupid in reality. In fact, the story goes like this: Before Xiao Ming left, he sent a text message to the staff of CCTV5 with his phone number. Staff MM promised him, NBA on the first broadcast immediately send information to inform Xiaoming. Xiao Hong, Xiao Qiang and Xiao Long are the same, their phone numbers are recorded in the subscriber roster, when the NBA starts, the staff MM will open the subscriber roster, through the phone numbers above, send a text message to inform them in turn.

In this example, sending SMS notifications is a typical release-subscribe model. Xiao Ming, Xiao Hong and other subscribers are subscribers. They subscribe to the NBA premiere. Cctv5, as the publisher, will traverse the subscriber roster of phone numbers at the appropriate time and post messages to subscribers in turn.

  • As you can see, there are obvious advantages to using the publish-subscribe pattern in this example.
  • Subscribers no longer need to call CCTV5 every day to inquire about the opening time, at the appropriate time, CCTV5 as a publisher will notify subscribers of these messages.
  • The subscriber and CCTV5 are no longer strongly coupled; when a new subscriber comes along, he simply leaves his cell phone number on CCTV5, and CCTV5 doesn’t care about anything about the subscriber, whether he is a man, a woman or a monkey. Any changes to CCTV5 will not affect subscribers, such as staff MM leaving, CCTV5 moving from Beijing to Shenzhen, these changes have nothing to do with subscribers, as long as CCTV5 remembers to send text messages.

Code examples:

In fact, as long as we’ve ever bound an event function to a DOM node, we’ve used the publis-subscribe model. Here’s what happens with these two simple lines of code:

document.body.addEventListener( 'click'.function(){
    alert(2);
}, false );

document.body.click(); // Simulate user click
Copy the code

Here we need to monitor when the user clicks document.body, but we have no way of predicting when the user will click. So we subscribe to the click event on document.body, and when the body node is clicked, the body node posts the message to the subscriber. It’s much like the case of buying a house, where the buyer doesn’t know when the house is for sale, so he feeds and waits for the sales office to post.

Of course, we can add or remove subscribers at will, adding any subscribers will not affect the writing of the publisher’s code:

document.body.addEventListener( 'click'.function(){
    alert(2);
}, false );
document.body.addEventListener( 'click'.function(){
    alert(3);
}, false );
document.body.addEventListener( 'click'.function(){
    alert(4);
}, false );

document.body.click(); // Simulate user click
Copy the code

A slightly more complete example

// Pull out the publish-and-subscribe functionality and put it in a separate object:
var event = {
    clientList: {}, / / the message list, a message can have multiple subscribers, 1 to many, such as {' NBA ': [' Ming', 'yellow', 'zhang'], 'the CBA: [' red', 'little blue]}
    // Subscription function
    listen: function( key, fn ){
        if(!this.clientList[ key ] ){
            this.clientList[ key ] = [];
        }
        this.clientList[ key ].push( fn ); // Add the message and the corresponding subscriber to the subscriber list
    },
    // Publish functionality
    trigger: function(){
        var key = Array.prototype.shift.call( arguments ), // Take the first parameter (message, such as 'NBA')
        fns = this.clientList[ key ]; // List of subscribers
        if ( !fns || fns.length === 0) {// If no one subscribs
            return false;
        }
        for( var i = 0, fn; fn = fns[ i++ ]; ) {// Inform subscribers 'NBA is on '
            fn.apply( this.arguments ); // Arguments are the arguments that trigger takes}}};// Define an installEvent function that dynamically installs publishing-subscribe functionality for all objects
var installEvent = function( obj ){
    for ( var i inevent ){ obj[ i ] = event[ i ]; }};// To test this again, let's add a dynamic publish-subscribe function to the TV station object:
var cctv5 = {}; // Television object
installEvent( cctv5 ); // Install a publish-subscribe function for the TV object

// Subscriber Xiao Ming, Xiao Hong, Xiao LAN
var ming = function( time ){ 
    console.log( time + 'NBA is on, I'm Xiaoming, I subscribe to NBA. ' );
}
var hong = function( time ){ 
    console.log( time + 'NBA is on, I'm Red, I subscribe to NBA. ' );
}
var lan = function( time ){ 
    console.log( time + 'CBA is on. I'm Xiao LAN. I subscribe to CBA. ' );
}

// Subscribe to the message
cctv5.listen( 'NBA', ming ); // Xiao Ming has subscribed to the premiere of NBA
cctv5.listen( 'NBA', hong ); // Xiao Hong has subscribed to the NBA premiere news

cctv5.listen( 'CBA', lan ); // Xiao LAN has subscribed to the news of CBA

// Publish the message
cctv5.trigger( 'NBA'.'ten in the morning' ); // Output: The NBA starts at 10 o 'clock in the morning. I am Xiaoming and I subscribe to the NBA. At 10 o 'clock in the morning, THE NBA starts. I'm Xiao Hong. I subscribe to the NBA
cctv5.trigger( 'CBA'.'7pm' ); // Output: CBA starts at 7 PM, I am a small C, I subscribe to CBA
Copy the code

Extend the functionality

// Take the code above and add it below

// Unsubscribe
event.remove = function( key, fn ){
    var fns = this.clientList[ key ];
    if ( !fns ){
        return false;
    }
    if ( !fn ){ // If no specific callback function (subscriber) is passed, all subscribers of the message corresponding to the key need to be unsubscribed. For example, all subscribers of 'NBA' need to be unsubscribed
        fns && ( fns.length = 0 );
    } else {
        for ( var l = fns.length - 1; l >=0; l-- ){ // Loop backwards through the list of subscribed callbacks
            var _fn = fns[ l ];
            if ( _fn === fn ){
                fns.splice( l, 1 ); // Delete the subscriber callback function}}}}; installEvent( cctv5 );// Re-install the functionality for the TV object

// Unsubscribe
cctv5.remove( 'NBA', ming ); // Xiao Ming canceled his subscription to the NBA

// Publish the message
cctv5.trigger( 'NBA'.'ten in the morning' ); // Output: The NBA starts at 10 o 'clock in the morning. I am Xiao Hong and I subscribe to the NBA
Copy the code

Personal arrangement, error can leave a message. Part of the reference series “JavaScript design Patterns and development practices” have explored


Sorting out other Common Design Patterns: Understanding the main Design Patterns (javascript implementation)

  1. The singleton pattern
  2. Iterator pattern
  3. Broker (mediation) pattern
  4. Decorator pattern