Se trata de un acoplamiento flexible y una 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.
El acoplamiento suelto 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 bueno porque puede reutilizar fácilmente los diferentes módulos. No está acoplado con las interfaces de otros módulos. Al usar publicar / suscribirse, solo se combina con la interfaz de publicación / suscripción, que no es un gran problema, solo dos métodos. Entonces, si decide reutilizar un módulo en un proyecto diferente, simplemente puede copiarlo y pegarlo y probablemente funcionará o al menos no necesitará mucho esfuerzo para hacerlo funcionar.
Cuando hablamos de acoplamiento flojo debemos mencionar la separación de preocupaciones. Si está creando una aplicación utilizando un patrón arquitectónico MV *, siempre tiene un Modelo (s) y una Vista (s). El modelo es la parte comercial de la aplicación. Puedes reutilizarlo en diferentes aplicaciones, por lo que no es buena idea acoplarlo con la Vista de una sola aplicación, donde quieras mostrarlo, porque generalmente en las diferentes aplicaciones tienes diferentes vistas. Por lo tanto, es una buena idea usar publicar / suscribirse para la comunicación Modelo-Vista. Cuando su modelo cambia, publica un evento, la vista lo detecta y se actualiza. No tiene ninguna sobrecarga de la publicación / suscripción, lo ayuda para el desacoplamiento. De la misma manera, puede mantener la lógica de su 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), simplemente publica un nuevo evento, el Controlador lo detecta y decide qué hacer. Si está familiarizado con elPatrón MVC o con MVVM en tecnologías de Microsoft (WPF / Silverlight) puede pensar en la publicación / suscripción como el patrón Observer . Este enfoque se utiliza en marcos 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 le gusta el enfoque MV *, puede usar algo un poco diferente (hay una intersección entre el que describiré a continuación y el último mencionado). Simplemente estructura tu aplicación en diferentes módulos. Por ejemplo, mire Twitter.
Si miras la interfaz, simplemente tienes diferentes cajas. Puede pensar en cada caja como un módulo diferente. Por ejemplo, puede publicar un tweet. Esta acción requiere la actualización de algunos módulos. En primer lugar, tiene que actualizar los datos de su perfil (cuadro superior izquierdo) pero también tiene que actualizar su línea de tiempo. Por supuesto, puede mantener referencias a ambos módulos y actualizarlos por separado usando 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 al acoplamiento más flojo. Si desarrolla un nuevo módulo que depende de nuevos tweets, simplemente puede suscribirse al evento "publicar-tweet" y manejarlo. Este enfoque es muy útil y puede hacer que su aplicación sea 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 mía):
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 de MV *, los mejores artículos y libros que conozco son publicados por Addy Osmani .
Inconvenientes: debe tener cuidado con el uso excesivo de publicar / suscribirse. Si tiene cientos de eventos, puede resultar muy confuso administrarlos todos. También puede tener colisiones si no usa el espacio de nombres (o no lo usa de la manera correcta). Una implementación avanzada de Mediator que se parece mucho a una publicación / suscripción se puede encontrar aquí https://github.com/ajacksified/Mediator.js . Tiene espacio de nombres y características como "burbujeo" de eventos que, por supuesto, se puede interrumpir. Otro inconveniente de publicar / suscribirse es la prueba unitaria dura, puede resultar difícil aislar las diferentes funciones en los módulos y probarlas de forma independiente.
El objetivo principal es reducir el acoplamiento entre el código. Es una forma de pensar un tanto basada en eventos, pero los "eventos" no están vinculados a un objeto específico.
Escribiré un gran ejemplo a continuación en un pseudocódigo que se parece un poco a JavaScript.
Digamos que tenemos una radio de clase y un relé de clase:
Siempre que la radio reciba una señal, queremos que varios relés transmitan el mensaje de alguna manera. El número y los tipos de relés pueden diferir. Podríamos hacerlo así:
Esto funciona bien. Pero ahora imagina que queremos un componente diferente para que también forme parte de las señales que recibe la clase Radio, a saber, los Altavoces:
(lo siento si las analogías no son de primera categoría ...)
Podríamos repetir el patrón nuevamente:
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 podamos 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. que decidamos y la clase Radio. Básicamente, siempre que cambie una de las clases de Radio, Señal o Retransmisión, debe pensar en cómo podría afectar a las otras dos clases.
Ahora intentemos algo diferente. Creemos una cuarta clase llamada RadioMast:
Ahora tenemos un patrón que conocemos y podemos usarlo para cualquier número y tipo de clases siempre que:
Así que cambiamos la clase Radio a su forma final y simple:
Y añadimos los altavoces y el relé a la lista de receptores del RadioMast para este tipo de señal:
Ahora la clase Speakers and Relay no tiene conocimiento de nada excepto que tienen un método que puede recibir una señal, y la clase Radio, siendo la editora, es consciente del RadioMast al que publica señales. Este es el punto de utilizar un sistema de paso de mensajes como publicar / suscribirse.
fuente
class
palabra clave. Por favor enfatice este hecho, ej. clasificando su código como pseudocódigo.Las otras respuestas han hecho un gran trabajo al mostrar cómo funciona el patrón. Quería abordar la pregunta implícita " ¿qué hay de malo en el método antiguo? ", Ya que he estado trabajando con este patrón recientemente y encuentro que implica un cambio en mi forma de pensar.
Imagina que nos hemos suscrito a un boletín económico. El boletín publica un titular: " Baje el Dow Jones en 200 puntos ". Sería un mensaje extraño y algo irresponsable de enviar. Sin embargo, si publicó: " Enron solicitó la protección por 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 eso es otro asunto.
Hay una diferencia entre enviar un comando y avisar de algo que acaba de suceder. Con esto en mente, tome su versión original del patrón pub / sub, ignorando el controlador por ahora:
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 su ejemplo, la acción está dando una orden. Considere esta versión:
Ahora el gestor está respondiendo a algo de interés que ha sucedido, pero no tiene la obligación de eliminar una orden. De hecho, el controlador puede hacer todo tipo de cosas que no están directamente relacionadas con la eliminación de una orden, pero que aún pueden ser relevantes para la acción de llamada. Por ejemplo:
La distinción entre un comando y una notificación es una distinción útil para hacer con este patrón, en mi opinión.
fuente
remindUserToFloss
&increaseProgrammerBrowniePoints
) estuvieran ubicadas en módulos separados, ¿publicaría 2 eventos uno después del otro allí mismohandleRemoveOrderRequest
o tendría queflossModule
publicar un evento en unbrowniePoints
módulo cuandoremindUserToFloss()
haya terminado?Para que no tenga que codificar las llamadas a métodos / funciones, simplemente publique el evento sin importar quién lo escuche. Esto hace que el editor sea independiente del suscriptor, lo que reduce la dependencia (o el acoplamiento, el término que prefiera) entre 2 partes diferentes de la aplicación.
Aquí hay algunas desventajas del acoplamiento como lo menciona wikipedia
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:
Ahora no puedo probar el objeto persona sin incluir también la
showAge
función. Además, si también necesito mostrar la edad en algún otro módulo GUI, necesito codificar esa llamada al método.setAge
, y ahora hay dependencias para 2 módulos no relacionados en el objeto persona. También es difícil de mantener cuando ves que se realizan esas llamadas y ni siquiera están en el mismo archivo.Tenga en cuenta que dentro del mismo módulo, por supuesto, puede tener llamadas directas a métodos. Pero los datos comerciales y el comportamiento superficial de la interfaz gráfica de usuario no deben residir en el mismo módulo según ningún estándar razonable.
fuente
removeOrder
existe, por lo que no puedes depender de él. En el segundo ejemplo, tienes que saberlo.La implementación de PubSub se ve comúnmente en donde hay:
Código de ejemplo -
fuente
El artículo "Las muchas caras de publicar / suscribir" es una buena lectura y una cosa que enfatizan es el desacoplamiento en tres "dimensiones". Aquí está mi resumen crudo, pero por favor haga referencia al artículo también.
fuente
Respuesta simple La pregunta original buscaba una respuesta simple. Este es mi intento.
Javascript no proporciona ningún mecanismo para que los objetos de código creen sus propios eventos. Entonces necesitas una especie de mecanismo de eventos. el patrón Publicar / suscribirse responderá a esta necesidad, y depende de usted elegir el mecanismo que mejor se adapte a sus necesidades.
Ahora podemos ver la necesidad del patrón pub / sub, entonces, ¿preferiría manejar los eventos DOM de manera diferente a como maneja sus eventos pub / sub? En aras de reducir la complejidad y otros conceptos como la separación de preocupaciones (SoC), es posible que vea el beneficio de que todo sea uniforme.
Entonces, paradójicamente, más código crea una mejor separación de preocupaciones, lo que se adapta bien a páginas web muy complejas.
Espero que alguien considere que esta es una discusión suficientemente buena sin entrar en detalles.
fuente