w3schools tag tab style page for color javascript jquery design-patterns publish-subscribe

javascript - tag - ¿Por qué uno usaría el patrón Publicar/Suscribir(en JS/jQuery)?



title of page html (6)

Entonces, un colega me presentó el patrón publicar / suscribir (en JS / jQuery), pero me está costando entender por qué uno usaría este patrón sobre JavaScript / jQuery ''normal''.

Por ejemplo, anteriormente tenía el siguiente código ...

$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(); } });

Y pude ver el mérito de hacer esto en su lugar, por ejemplo ...

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'')); });

Porque presenta la capacidad de reutilizar la funcionalidad removeOrder para diferentes eventos, etc.

¿Pero por qué decidir implementar el patrón de publicación / suscripción y llegar a las siguientes longitudes, si hace lo mismo? (Para su información, he utilizado jQuery pequeño 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'')); });

He leído sobre el patrón con seguridad, pero no puedo imaginar por qué esto sería necesario. Los tutoriales que he visto que explican cómo implementar este patrón solo cubren ejemplos tan básicos como el mío.

Imagino que la utilidad del pub / sub se haría aparente en una aplicación más compleja, pero no puedo imaginar una. Me temo que estoy completamente perdido el punto; pero me gustaría saber el punto si hay alguno!

¿Podría explicar de manera sucinta por qué y en qué situaciones este patrón es ventajoso? ¿Vale la pena utilizar el patrón pub / sub para los fragmentos de código como los ejemplos anteriores?


El documento "Las muchas caras de publicar / suscribir" es una buena lectura y una de las cosas que destacan es el desacoplamiento en tres "dimensiones". Aquí está mi resumen crudo, pero por favor vea el papel en su lugar.

  1. Disociación del espacio Las partes que interactúan no necesitan conocerse entre sí. El editor no sabe quién está escuchando, cuántos o qué están haciendo con el evento. Los suscriptores no saben quién está produciendo estos eventos o cuántos productores hay, etc.
  2. Tiempo de disociación Las partes que interactúan no necesitan estar activas al mismo tiempo durante la interacción. Por ejemplo, un suscriptor puede desconectarse mientras un editor está publicando algunos eventos, y puede reaccionar ante él cuando esté en línea.
  3. Desacoplamiento de sincronización. Los editores no están bloqueados mientras producen eventos y los suscriptores pueden ser notificados de forma asincrónica a través de devoluciones de llamadas cuando llega un evento al que han suscrito.

El objetivo principal es reducir el acoplamiento entre el código. Es una forma de pensar basada en eventos, pero los "eventos" no están vinculados a un objeto específico.

Voy a escribir un gran ejemplo a continuación en un pseudo código que se parece un poco a JavaScript.

Digamos que tenemos una clase Radio y una clase Relay:

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? } }

Cada vez que la radio recibe una señal, queremos una serie de retransmisiones para retransmitir el mensaje de alguna manera. La cantidad y los tipos de relés pueden diferir. Podríamos hacerlo así:

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

Esto funciona bien Pero ahora imagine que queremos que un componente diferente también tome parte de las señales que recibe la clase de Radio, es decir, los Parlantes:

(Lo siento si las analogías no son de primera clase ...)

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

Podríamos repetir el patrón de nuevo:

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); } } }

Podríamos hacer esto aún mejor creando una interfaz, como "SignalListener", de modo que solo necesitemos una lista en la clase Radio, y siempre podemos llamar a la misma función en cualquier objeto que tengamos que quiera escuchar la señal. Pero eso aún crea un acoplamiento entre cualquier interfaz / clase base / etc. en la que decidamos y la clase de Radio. Básicamente cada vez que cambie una de las clases de Radio, Señal o Retransmisión, debe pensar en cómo podría afectar las otras dos clases.

Ahora intentemos algo diferente. Vamos a crear una cuarta clase llamada 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); } } }

Ahora tenemos un patrón del que somos conscientes y podemos usarlo para cualquier cantidad y tipo de clases siempre que:

  • están al tanto de RadioMast (la clase que maneja todo el mensaje que pasa)
  • conocen la firma del método para enviar / recibir mensajes

Así que cambiamos la clase de Radio a su forma final y simple:

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

Y agregamos los altavoces y el relé a la lista de receptores de RadioMast para este tipo de señal:

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

Ahora la clase Parlantes y Retransmisores no tiene conocimiento de nada excepto que tienen un método que puede recibir una señal, y la clase de Radio, que es el editor, conoce el RadioMast al que le envía señales. Este es el punto de usar un sistema de transmisión de mensajes como publicar / suscribir.


La implementación de PubSub se ve comúnmente en donde hay:

  1. Hay una implementación tipo portlet en la que hay múltiples portlets que se comunican con la ayuda de un bus de eventos. Esto ayuda a crear en la arquitectura aync.
  2. En un sistema marcado por un acoplamiento cerrado, pubsub es un mecanismo que ayuda a la comunicación entre varios módulos.

Código de ejemplo -

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.


Las otras respuestas han hecho un gran trabajo al mostrar cómo funciona el patrón. Quería abordar la pregunta implícita " ¿qué pasa con el viejo modo? ", Ya que he estado trabajando con este patrón recientemente, y creo que implica un cambio en mi forma de pensar.

Imagine que nos hemos suscrito a un boletín económico. El boletín publica un titular: " Baje el Dow Jones en 200 puntos ". Ese sería un mensaje extraño e irresponsable de enviar. Sin embargo, si publicó: " Enron solicitó la protección de bancarrota del capítulo 11 esta mañana ", entonces este es un mensaje más útil. Tenga en cuenta que el mensaje puede hacer que el Dow Jones caiga 200 puntos, pero ese es otro asunto.

Existe una diferencia entre enviar un comando y avisar algo que acaba de suceder. Con esto en mente, tome su versión original del patrón pub / sub, ignorando el controlador por ahora:

$.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'')); });

Ya existe un fuerte acoplamiento implícito aquí, entre la acción del usuario (un clic) y la respuesta del sistema (una orden que se elimina). Efectivamente en tu ejemplo, la acción está dando un comando. Considera esta versión:

$.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'')); });

Ahora el controlador está respondiendo a algo de interés que ha sucedido, pero no tiene la obligación de eliminar un pedido. De hecho, el controlador puede hacer todo tipo de cosas que no estén directamente relacionadas con la eliminación de un pedido, pero que aún así sean relevantes para la acción de llamada. Por ejemplo:

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... }

La distinción entre un comando y una notificación es una distinción útil para hacer con este patrón, IMO.


Para que no tenga que codificar las llamadas al método / función, simplemente publique el evento sin importar quién escuche. Esto hace que el editor sea independiente del suscriptor, reduciendo la dependencia (o acoplamiento, sea cual sea el término que prefiera) entre 2 partes diferentes de la aplicación.

Estas son algunas de las desventajas del acoplamiento mencionadas por wikipedia

Los sistemas fuertemente acoplados tienden a exhibir las siguientes características de desarrollo, que a menudo se consideran desventajas:

  1. Un cambio en un módulo generalmente obliga a un efecto dominó de los cambios en otros módulos.
  2. El ensamblaje de módulos puede requerir más esfuerzo y / o tiempo debido a la mayor dependencia entre módulos.
  3. Un módulo particular puede ser más difícil de reutilizar y / o probar porque los módulos dependientes deben ser incluidos.

Considere algo así como un objeto que encapsula datos comerciales. Tiene una llamada de método codificada para actualizar la página cada vez que se establece la edad:

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

Ahora no puedo probar el objeto persona sin incluir también la función showAge . Además, si necesito mostrar la edad en algún otro módulo GUI, necesito codificar esa llamada de método en .setAge , y ahora hay dependencias para 2 módulos no relacionados en el objeto de persona. También es difícil de mantener cuando ves esas llamadas y ni siquiera están en el mismo archivo.

Tenga en cuenta que dentro del mismo módulo, puede, por supuesto, tener llamadas a métodos directos. Pero los datos comerciales y el comportamiento gui superficial no deberían residir en el mismo módulo con estándares razonables.


Todo se trata de acoplamiento flexible y responsabilidad única, que va de la mano con los patrones MV * (MVC / MVP / MVVM) en JavaScript que son muy modernos en los últimos años.

Loose coupling es un principio orientado a objetos en el que cada componente del sistema conoce su responsabilidad y no se preocupa por los otros componentes (o al menos trata de no preocuparse por ellos tanto como sea posible). El acoplamiento flojo es una buena cosa porque puede reutilizar fácilmente los diferentes módulos. No está acoplado con las interfaces de otros módulos. Al utilizar publicar / suscribir, solo se combina con la interfaz de publicación / suscripción, que no es un gran problema, solo dos métodos. Entonces, si decides reutilizar un módulo en un proyecto diferente, puedes copiarlo y pegarlo, y probablemente funcione o al menos no necesitarás mucho esfuerzo para hacerlo funcionar.

Cuando hablamos de acoplamiento débil, debemos mencionar la separación de las preocupaciones . Si está creando una aplicación utilizando un patrón arquitectónico MV * siempre tendrá un Modelo (s) y una (s) Vista (s). El modelo es la parte comercial de la aplicación. Puede reutilizarlo en diferentes aplicaciones, por lo que no es una buena idea combinarlo con la Vista de una sola aplicación, donde quiera mostrarlo, porque generalmente en las diferentes aplicaciones tiene diferentes vistas. Por lo tanto, es una buena idea usar publicar / suscribir para la comunicación Model-View. Cuando su modelo lo cambia, publica un evento, la vista lo capta y se actualiza a sí mismo. No tiene ninguna sobrecarga desde la publicación / suscripción, lo ayuda a desacoplar. De la misma manera, puede mantener su lógica de aplicación en el Controlador, por ejemplo (MVVM, MVP no es exactamente un Controlador) y mantener la Vista lo más simple posible. Cuando su Vista cambia (o el usuario hace clic en algo, por ejemplo) solo publica un nuevo evento, el Controlador lo detecta y decide qué hacer. Si está familiarizado con el patrón MVC o con MVVM en las tecnologías de Microsoft (WPF / Silverlight), puede pensar en publicar / suscribirse como el patrón Observer . Este enfoque se usa en frameworks como Backbone.js, Knockout.js (MVVM).

Aquí hay un ejemplo:

//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); } } }());

Otro ejemplo. Si no te gusta el enfoque MV *, puedes usar algo un poco diferente (hay una intersección entre la que describiré a continuación y la última mencionada). Simplemente estructura tu aplicación en diferentes módulos. Por ejemplo, mira Twitter.

Si miras la interfaz, simplemente tienes diferentes cuadros. Puedes pensar en cada caja como un módulo diferente. Por ejemplo, puedes publicar un tweet. Esta acción requiere la actualización de algunos módulos. En primer lugar, debe actualizar los datos de su perfil (cuadro superior izquierdo), pero también debe actualizar su línea de tiempo. Por supuesto, puede mantener referencias a ambos módulos y actualizarlos por separado utilizando su interfaz pública, pero es más fácil (y mejor) publicar un evento. Esto facilitará la modificación de su aplicación debido a un acoplamiento más flexible. Si desarrolla un nuevo módulo que depende de los nuevos tweets, puede suscribirse al evento "publish-tweet" y manejarlo. Este enfoque es muy útil y puede hacer que su aplicación esté muy desacoplada. Puede reutilizar sus módulos muy fácilmente.

Aquí hay un ejemplo básico del último enfoque (este no es el código original de Twitter, es solo una muestra):

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); }); } }; }());

Para este enfoque hay una excelente charla de Nicholas Zakas . Para el enfoque MV *, los mejores artículos y libros que conozco son publicados por Addy Osmani .

Inconvenientes: debe tener cuidado con el uso excesivo de publicar / suscribir. Si tienes cientos de eventos, puede ser muy confuso administrarlos todos. También puede tener colisiones si no está utilizando el espacio de nombres (o si no lo usa de la manera correcta). Puede encontrar una implementación avanzada de Mediator que se parece mucho a una publicación / suscripción aquí https://github.com/ajacksified/Mediator.js . Tiene espacios de nombres y funciones como el evento "burbujeo" que, por supuesto, se puede interrumpir. Otro inconveniente de publicar / suscribir es la prueba de unidad dura, puede ser difícil aislar las diferentes funciones en los módulos y probarlos de manera independiente.