Tout est question de couplage lâche et de responsabilité unique, qui vont de pair avec les modèles MV * (MVC / MVP / MVVM) en JavaScript qui sont très modernes ces dernières années.
Le couplage lâche est un principe orienté objet dans lequel chaque composant du système connaît sa responsabilité et ne se soucie pas des autres composants (ou du moins essaie de ne pas s'en soucier autant que possible). Un couplage lâche est une bonne chose car vous pouvez facilement réutiliser les différents modules. Vous n'êtes pas couplé avec les interfaces d'autres modules. En utilisant la publication / l'abonnement, vous êtes uniquement associé à l'interface de publication / abonnement, ce qui n'est pas un gros problème - juste deux méthodes. Donc, si vous décidez de réutiliser un module dans un projet différent, vous pouvez simplement le copier et le coller et cela fonctionnera probablement ou du moins vous n'aurez pas besoin de beaucoup d'efforts pour le faire fonctionner.
Quand on parle de couplage lâche, il faut mentionner la séparation des préoccupations. Si vous créez une application à l'aide d'un modèle architectural MV *, vous disposez toujours d'un ou plusieurs modèles et d'une ou plusieurs vues. Le modèle est la partie métier de l'application. Vous pouvez le réutiliser dans différentes applications, donc ce n'est pas une bonne idée de le coupler avec la vue d'une seule application, là où vous voulez l'afficher, car généralement dans les différentes applications, vous avez des vues différentes. C'est donc une bonne idée d'utiliser la publication / l'abonnement pour la communication Model-View. Lorsque votre modèle change, il publie un événement, la vue le capture et se met à jour. Vous n'avez aucune surcharge de publication / abonnement, cela vous aide pour le découplage. De la même manière, vous pouvez conserver la logique de votre application dans le contrôleur par exemple (MVVM, MVP ce n'est pas exactement un contrôleur) et garder la vue aussi simple que possible. Lorsque votre vue change (ou que l'utilisateur clique sur quelque chose, par exemple), il publie simplement un nouvel événement, le contrôleur l'attrape et décide quoi faire. Si vous connaissez leModèle MVC ou avec MVVM dans les technologies Microsoft (WPF / Silverlight), vous pouvez considérer la publication / l'abonnement comme le modèle Observer . Cette approche est utilisée dans des frameworks tels que Backbone.js, Knockout.js (MVVM).
Voici un exemple:
//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);
}
}
}());
Un autre exemple. Si vous n'aimez pas l'approche MV *, vous pouvez utiliser quelque chose d'un peu différent (il y a une intersection entre celle que je décrirai ensuite et la dernière mentionnée). Structurez simplement votre application en différents modules. Par exemple, regardez Twitter.
Si vous regardez l'interface, vous avez simplement différentes cases. Vous pouvez considérer chaque boîte comme un module différent. Par exemple, vous pouvez publier un tweet. Cette action nécessite la mise à jour de quelques modules. Tout d'abord, il doit mettre à jour vos données de profil (case en haut à gauche) mais il doit également mettre à jour votre chronologie. Bien sûr, vous pouvez conserver les références aux deux modules et les mettre à jour séparément à l'aide de leur interface publique, mais il est plus facile (et meilleur) de simplement publier un événement. Cela facilitera la modification de votre application en raison d'un couplage plus lâche. Si vous développez un nouveau module qui dépend de nouveaux tweets, vous pouvez simplement vous abonner à l'événement «publish-tweet» et le gérer. Cette approche est très utile et peut rendre votre application très découplée. Vous pouvez réutiliser vos modules très facilement.
Voici un exemple de base de la dernière approche (ce n'est pas du code Twitter original, c'est juste un exemple de ma part):
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);
});
}
};
}());
Pour cette approche, il y a un excellent discours de Nicholas Zakas . Pour l'approche MV *, les meilleurs articles et livres que je connaisse sont publiés par Addy Osmani .
Inconvénients: vous devez faire attention à l'utilisation excessive de publication / abonnement. Si vous avez des centaines d'événements, il peut devenir très déroutant de les gérer tous. Vous pouvez également avoir des collisions si vous n'utilisez pas d'espacement de noms (ou si vous ne l'utilisez pas de la bonne manière). Une implémentation avancée de Mediator qui ressemble beaucoup à une publication / abonnement peut être trouvée ici https://github.com/ajacksified/Mediator.js . Il a un espace de noms et des fonctionnalités telles que le «bouillonnement» d'événement qui, bien sûr, peut être interrompu. Un autre inconvénient de publier / souscrire est le test unitaire dur, il peut devenir difficile d'isoler les différentes fonctions dans les modules et de les tester indépendamment.
L'objectif principal est de réduire le couplage entre le code. C'est une façon de penser quelque peu basée sur les événements, mais les «événements» ne sont pas liés à un objet spécifique.
Je vais écrire un grand exemple ci-dessous dans un pseudo-code qui ressemble un peu à JavaScript.
Disons que nous avons une radio de classe et un relais de classe:
Chaque fois que la radio reçoit un signal, nous voulons qu'un certain nombre de relais relaient le message d'une manière ou d'une autre. Le nombre et les types de relais peuvent différer. Nous pourrions le faire comme ceci:
Cela fonctionne très bien. Mais maintenant, imaginez que nous voulons qu'un composant différent prenne également part aux signaux que la classe Radio reçoit, à savoir les haut-parleurs:
(désolé si les analogies ne sont pas de premier ordre ...)
Nous pourrions répéter le modèle à nouveau:
Nous pourrions rendre cela encore meilleur en créant une interface, comme "SignalListener", de sorte que nous n'ayons besoin que d'une seule liste dans la classe Radio, et que nous puissions toujours appeler la même fonction sur n'importe quel objet que nous avons qui veut écouter le signal. Mais cela crée toujours un couplage entre l'interface / la classe de base / etc que nous choisissons et la classe Radio. Fondamentalement, chaque fois que vous changez l'une des classes Radio, Signal ou Relay, vous devez réfléchir à la façon dont cela pourrait éventuellement affecter les deux autres classes.
Essayons maintenant quelque chose de différent. Créons une quatrième classe nommée RadioMast:
Nous avons maintenant un modèle dont nous sommes conscients et nous pouvons l'utiliser pour n'importe quel nombre et types de classes tant qu'ils:
Nous changeons donc la classe Radio dans sa forme finale et simple:
Et nous ajoutons les haut-parleurs et le relais à la liste des récepteurs de RadioMast pour ce type de signal:
Maintenant, la classe Speakers and Relay n'a aucune connaissance de quoi que ce soit, sauf qu'elle a une méthode qui peut recevoir un signal, et la classe Radio, en tant qu'éditeur, est consciente du RadioMast sur lequel elle publie des signaux. C'est le point d'utiliser un système de transmission de messages tel que publier / s'abonner.
la source
class
mot - clé. Veuillez souligner ce fait, par exemple. en classant votre code en pseudo-code.Les autres réponses ont fait un excellent travail en montrant comment le modèle fonctionne. Je voulais aborder la question implicite " qu'est-ce qui ne va pas avec l'ancienne méthode? " Car j'ai travaillé avec ce modèle récemment, et je trouve que cela implique un changement dans ma pensée.
Imaginez que nous nous sommes abonnés à un bulletin économique. Le bulletin publie un titre: " Baisser le Dow Jones de 200 points ". Ce serait un message étrange et quelque peu irresponsable à envoyer. Si toutefois, il a publié: " Enron a demandé la protection contre la faillite du chapitre 11 ce matin ", alors c'est un message plus utile. Notez que le message peut faire chuter le Dow Jones de 200 points, mais c'est une autre affaire.
Il y a une différence entre envoyer une commande et signaler quelque chose qui vient de se passer. Dans cet esprit, prenez votre version originale du modèle pub / sub, en ignorant le gestionnaire pour le moment:
Il y a déjà ici un couplage fort implicite, entre l'action utilisateur (un clic) et la réponse système (une commande en cours de suppression). Efficacement dans votre exemple, l'action donne une commande. Considérez cette version:
Maintenant, le gestionnaire répond à un événement intéressant qui s'est produit, mais n'est pas obligé de retirer une commande. En fait, le gestionnaire peut faire toutes sortes de choses qui ne sont pas directement liées à la suppression d'une commande, mais qui peuvent toujours être pertinentes pour l'action d'appel. Par exemple:
La distinction entre une commande et une notification est une distinction utile à faire avec ce modèle, IMO.
la source
remindUserToFloss
&increaseProgrammerBrowniePoints
) étaient situées dans des modules séparés, publieriez-vous 2 événements l'un après l'autre juste làhandleRemoveOrderRequest
ou auriez-vous uneflossModule
publication d'un événement dans unbrowniePoints
module lorsque celaremindUserToFloss()
est fait?Pour ne pas avoir à coder en dur les appels de méthode / fonction, il vous suffit de publier l'événement sans vous soucier de qui écoute. Cela rend l'éditeur indépendant de l'abonné, ce qui réduit la dépendance (ou le couplage, quel que soit le terme que vous préférez) entre 2 parties différentes de l'application.
Voici quelques inconvénients du couplage comme mentionné par wikipedia
Considérez quelque chose comme un objet encapsulant des données d'entreprise. Il a un appel de méthode codé en dur pour mettre à jour la page chaque fois que l'âge est défini:
Maintenant, je ne peux pas tester l'objet personne sans inclure également la
showAge
fonction. De plus, si j'ai besoin d'afficher l'âge dans un autre module d'interface graphique, je dois coder en dur cet appel de méthode.setAge
, et maintenant il y a des dépendances pour 2 modules non liés dans l'objet personne. Il est également difficile à maintenir lorsque vous voyez que ces appels sont effectués et qu'ils ne sont même pas dans le même fichier.Notez qu'à l'intérieur du même module, vous pouvez bien sûr avoir des appels de méthode directs. Mais les données commerciales et le comportement superficiel de l'interface graphique ne devraient pas résider dans le même module selon des normes raisonnables.
la source
removeOrder
cela existe même, vous ne pouvez donc pas en dépendre. Dans le deuxième exemple, vous devez savoir.La mise en œuvre de PubSub est généralement observée là où il y a -
Exemple de code -
la source
Le papier «Les nombreux visages de publier / souscrire» est une bonne lecture et une chose sur laquelle ils insistent est le découplage en trois «dimensions». Voici mon résumé grossier, mais veuillez également faire référence au document.
la source
Réponse simple La question initiale cherchait une réponse simple. Voici ma tentative.
Javascript ne fournit aucun mécanisme permettant aux objets de code de créer leurs propres événements. Vous avez donc besoin d'une sorte de mécanisme d'événement. le modèle Publier / S'abonner répondra à ce besoin, et c'est à vous de choisir le mécanisme qui correspond le mieux à vos propres besoins.
Maintenant, nous pouvons voir un besoin pour le modèle pub / sub, alors préférez-vous gérer les événements DOM différemment de la façon dont vous gérez vos événements pub / sub? Dans le but de réduire la complexité et d'autres concepts tels que la séparation des préoccupations (SoC), vous pourriez voir l'avantage que tout soit uniforme.
Donc, paradoxalement, plus de code crée une meilleure séparation des préoccupations, qui évolue bien jusqu'à des pages Web très complexes.
J'espère que quelqu'un trouvera cette discussion assez bonne sans entrer dans les détails.
la source