Why use a publish / subscribe template (in JS / jQuery)?

So, a colleague introduced me to the publish / subscribe template (in JS / jQuery), but it’s hard for me to cope with why this template can be used for “normal” JavaScript / jQuery.

For example, I used to have the following code ...

$container.on('click', '.remove_order', function(event) { event.preventDefault(); var orders = $(this).parents('form:first').find('div.order'); if (orders.length > 2) { orders.last().remove(); } }); 

And I could see the dignity of doing this, for example ...

 removeOrder = function(orders) { if (orders.length > 2) { orders.last().remove(); } } $container.on('click', '.remove_order', function(event) { event.preventDefault(); removeOrder($(this).parents('form:first').find('div.order')); }); 

As it introduces the ability to reuse removeOrder functionality for different events, etc.

But why did you decide to implement the publication / subscription template and move on to the next length if it does the same? (FYI, I used jQuery tiny pub / sub )

 removeOrder = function(e, orders) { if (orders.length > 2) { orders.last().remove(); } } $.subscribe('iquery/action/remove-order', removeOrder); $container.on('click', '.remove_order', function(event) { event.preventDefault(); $.publish('iquery/action/remove-order', $(this).parents('form:first').find('div.order')); }); 

I read about the template for sure, but I just can't imagine why this would be necessary. The tutorials I saw that explain how to implement this template cover as basic examples as my own.

I assume that the usefulness of pub / sub will become apparent in a more complex application, but I cannot imagine it. I am afraid that I do not understand the point; but I would like to know if he is!

Could you explain succinctly why and in what situations this template is beneficial? Should I use the pub / sub template for code snippets like my examples above?

+83
javascript jquery design-patterns publish-subscribe
Nov 22 '12 at 12:39
source share
5 answers

Its all about loose communication and a single responsibility that goes hand in hand with MV * patterns (MVC / MVP / MVVM) in JavaScript, which are very modern in the last few years.

Loose communication is an object-oriented principle in which each component of the system knows its responsibility and does not care about other components (or at least tries not to care about them as much as possible). Loose communication is good, because you can easily use different modules. You are not connected to the interfaces of other modules. Using publish / subscribe youre only in combination with the publish / subscribe interface, which does not really matter - there are only two methods. Therefore, if you decide to reuse the module in another project, you can simply copy and paste it and it will probably work, or at least you will not need much effort to get it to work.

Speaking of loose communication, we should mention the separation of problems. If you are creating an application using the MV * architectural template, you always have the model and view (s). The model is a business part of the application. You can reuse it in different applications, so it is not recommended to combine it with the idea of ​​one application where you want to show it, because usually you have different views in different applications. Therefore, it’s nice to use the publication / subscription to the Model-View link. When your model changes, it publishes an event, the view catches it and updates it. You do not have the overhead of publishing / subscribing, this will help you figure it out. In the same way, you can store application logic in a controller, for example (MVVM, MVP is not a controller at all) and keep the presentation as simple as possible. When your view changes (or the user clicks on something, for example), he simply publishes a new event, the controller catches it and decides what to do. If you are familiar with Microsoft's MVC or MVVM pattern (WPF / Silverlight), you may think of publishing / subscribing as an observer pattern . This approach is used in such structures as Backbone.js, Knockout.js (MVVM).

Here is an example:

 //Model function Book(name, isbn) { this.name = name; this.isbn = isbn; } function BookCollection(books) { this.books = books; } BookCollection.prototype.addBook = function (book) { this.books.push(book); $.publish('book-added', book); return book; } BookCollection.prototype.removeBook = function (book) { var removed; if (typeof book === 'number') { removed = this.books.splice(book, 1); } for (var i = 0; i < this.books.length; i += 1) { if (this.books[i] === book) { removed = this.books.splice(i, 1); } } $.publish('book-removed', removed); return removed; } //View var BookListView = (function () { function removeBook(book) { $('#' + book.isbn).remove(); } function addBook(book) { $('#bookList').append('<div id="' + book.isbn + '">' + book.name + '</div>'); } return { init: function () { $.subscribe('book-removed', removeBook); $.subscribe('book-aded', addBook); } } }()); 

Another example. If you don't like the MV * approach, you can use something a little different (theres the intersection between one Ill describes the next and last mentioned). Just structure your application in different modules. For example, look at Twitter.

Twitter Modules

If you look at the interface, you just have different boxes. You can think of each box as a different module. For example, you can tweet. This action requires updating several modules. First, it should update your profile data (upper left flag), but it should also update your timeline. Of course, you can store links to both modules and update them separately using your public interface, but it’s easier (and better) to simply post the event. This will make it easy to change your application due to a weakened connection. If you are developing a new module that depends on new tweets, you can simply subscribe to the publish-tweet event and process it. This approach is very useful and can make your application very loose. You can easily use your modules.

Here is a basic example of the latter approach (this is not the original twitter code, but only a sample):

 var Twitter.Timeline = (function () { var tweets = []; function publishTweet(tweet) { tweets.push(tweet); //publishing the tweet }; return { init: function () { $.subscribe('tweet-posted', function (data) { publishTweet(data); }); } }; }()); var Twitter.TweetPoster = (function () { return { init: function () { $('#postTweet').bind('click', function () { var tweet = $('#tweetInput').val(); $.publish('tweet-posted', tweet); }); } }; }()); 

There is an excellent conversation between Nikolai Zakas for this approach. For the MV * approach, the best articles and books I know are published by Addy Osmani .

Disadvantages: you must be careful with excessive use of publication / subscription. If you have hundreds of events, it can become very confusing to manage all of them. You may also have conflicts if you are not using the namespace (or not using it correctly). An advanced mediator implementation that looks like a publication / subscription is here https://github.com/ajacksified/Mediator.js . It has a namespace and functions, such as a bubble event, which, of course, can be interrupted. Another disadvantage of publishing / subscribing is rigorous testing; it can be difficult to isolate various functions in modules and test them yourself.

+197
Nov 22 '12 at 13:38
source share

The main goal is to reduce the connection between the code. This is a somewhat event-based way of thinking, but "events" are not tied to a specific object.

I will write a great example below in some pseudo code that is a bit like JavaScript.

Say we have a Radio class and a Relay class:

 class Relay { function RelaySignal(signal) { //do something we don't care about right now } } class Radio { function ReceiveSignal(signal) { //how do I send this signal to other relays? } } 

Whenever a radio receiver receives a signal, we want several relays to somehow transmit a message. The number and types of relays may vary. We could do it as follows:

 class Radio { var relayList = []; function AddRelay(relay) { relayList.add(relay); } function ReceiveSignal(signal) { for(relay in relayList) { relay.Relay(signal); } } } 

It works great. But now imagine that we want another component to also take part in the signals received by the Radio class, namely: Speakers:

(sorry if the analogies are not the top label ...)

 class Speakers { function PlaySignal(signal) { //do something with the signal to create sounds } } 

We can repeat the pattern again:

 class Radio { var relayList = []; var speakerList = []; function AddRelay(relay) { relayList.add(relay); } function AddSpeaker(speaker) { speakerList.add(speaker) } function ReceiveSignal(signal) { for(relay in relayList) { relay.Relay(signal); } for(speaker in speakerList) { speaker.PlaySignal(signal); } } } 

We could do it even better by creating an interface, for example, "SignalListener", so that we only need one list in the Radio class, and it can always call the same function on any object that we have that wants to listen to the signal But this still creates a connection between any interface / base class / etc that we accept and the Radio class. Basically, when you change one of the Radio, Signal, or Relay classes, you need to think about how this can affect the other two classes.

Now try something else. Let me create a fourth class called RadioMast:

 class RadioMast { var receivers = []; //this is the "subscribe" function RegisterReceivers(signaltype, receiverMethod) { //if no list for this type of signal exits, create it if(receivers[signaltype] == null) { receivers[signaltype] = []; } //add a subscriber to this signal type receivers[signaltype].add(receiverMethod); } //this is the "publish" function Broadcast(signaltype, signal) { //loop through all receivers for this type of signal //and call them with the signal for(receiverMethod in receivers[signaltype]) { receiverMethod(signal); } } } 

Now we have a template that we know about, and we can use it for any number and types of classes, if they:

  • know about RadioMast (a class that processes all messages)
  • know about the signature of the method for sending / receiving messages

So, we are changing the Radio class to its final, simple form:

 class Radio { function ReceiveSignal(signal) { RadioMast.Broadcast("specialradiosignal", signal); } } 

And add speakers and relays to the list of RadioMast receivers for this type of signal:

 RadioMast.RegisterReceivers("specialradiosignal", speakers.PlaySignal); RadioMast.RegisterReceivers("specialradiosignal", relay.RelaySignal); 

Now the Speakers and Relay class has zero knowledge of anything, except that they have a method that can receive a signal, and the Radio class, which is the publisher, knows RadioMast that it publishes the signals. This is the point of use for a messaging system such as publish / subscribe.

+13
Nov 22 '12 at 13:29
source share

Other answers did a great job of showing how the template works. I wanted to address the implied question “ what is wrong with the old way? ” Since I recently worked with this template, and I believe that this is due to a change in my thinking.

Imagine we signed up for an economic newsletter. The newsletter publishes a headline: "Lower the Dow Jones by 200 points." This would be a strange and somewhat irresponsible message to send. If, however, he posted: "Enron filed for Chapter 11 bankruptcy protection this morning," this is a more useful message. Please note that the message may cause the Dow Jones to fall by 200 points, but that is another matter.

There is a difference between sending a team and advice about something that just happened. With that in mind, take the original version of the pub / sub template, ignoring this handler:

 $.subscribe('iquery/action/remove-order', removeOrder); $container.on('click', '.remove_order', function(event) { event.preventDefault(); $.publish('iquery/action/remove-order', $(this).parents('form:first').find('div.order')); }); 

This already implies a strong connection between the user action (click) and the system response (deleted order). Effectively in your example, the action gives the command. Consider this version:

 $.subscribe('iquery/action/remove-order-requested', handleRemoveOrderRequest); $container.on('click', '.remove_order', function(event) { event.preventDefault(); $.publish('iquery/action/remove-order-requested', $(this).parents('form:first').find('div.order')); }); 

Now the handler is responding to some kind of interest that has occurred, but is not required to cancel the order. In fact, the handler can do all kinds of things that are not directly related to the removal of the order, but still, possibly related to the calling action. For example:

 handleRemoveOrderRequest = function(e, orders) { logAction(e, "remove order requested"); if( !isUserLoggedIn()) { adviseUser("You need to be logged in to remove orders"); } else if (isOkToRemoveOrders(orders)) { orders.last().remove(); adviseUser("Your last order has been removed"); logAction(e, "order removed OK"); } else { adviseUser("Your order was not removed"); logAction(e, "order not removed"); } remindUserToFloss(); increaseProgrammerBrowniePoints(); //etc... } 

The distinction between team and notification is a useful distinction to make with this template, IMO.

+5
Sep 24 '15 at 21:06
source share

So that you do not have to hard code the calls of methods / functions, you simply publish this event without worrying about who is listening. This makes the publisher independent of the subscriber, reducing the dependency (or communication, no matter which you prefer) between the two different parts of the application.

Here are some clutch flaws mentioned by wikipedia

Tightly coupled systems typically exhibit the following development characteristics, which are often considered disadvantages:

  • A change in one module usually causes the effect of pulsation of changes in other modules.
  • Assembling modules may require more effort and / or time due to increased inter-module dependency.
  • A particular module may be more difficult to repeat and / or verify because dependent modules must be included.

Consider something like an object that encapsulates business data. It has a hard-coded method to call page refresh when it reaches the age of:

 var person = { name: "John", age: 23, setAge: function( age ) { this.age = age; showAge( age ); } }; //Different module function showAge( age ) { $("#age").text( age ); } 

Now I can not check the person object, not including the showAge function. Also, if I need to show age in some other GUI, I need to hard .setAge call to this .setAge method, and now there are dependencies for 2 unrelated modules in the person object. It is also just hard to maintain when you see that these calls are made and they are not even in the same file.

Note that inside the same module you can, of course, have direct method calls. But gui's business data and superficial behavior should not be in the same module by any reasonable standards.

+4
Nov 22 '12 at 12:50
source share

PubSub implementation is usually found where there is -

  • There is such a portlet as an implementation where there are several portlets that communicate via the event bus. This helps build in aync architecture.
  • In a system clouded by hardwire, pubsub is a mechanism that helps in communication between the various modules.

Code Example -

 var pubSub = {}; (function(q) { var messages = []; q.subscribe = function(message, fn) { if (!messages[message]) { messages[message] = []; } messages[message].push(fn); } q.publish = function(message) { /* fetch all the subscribers and execute*/ if (!messages[message]) { return false; } else { for (var message in messages) { for (var idx = 0; idx < messages[message].length; idx++) { if (messages[message][idx]) messages[message][idx](); } } } } })(pubSub); pubSub.subscribe("event-A", function() { console.log('this is A'); }); pubSub.subscribe("event-A", function() { console.log('booyeah A'); }); pubSub.publish("A"); //executes the methods. 
0
Apr 23 '17 at 17:41 on
source share



All Articles