Es geht um lose Kopplung und Einzelverantwortung, was mit MV * -Mustern (MVC / MVP / MVVM) in JavaScript einhergeht, die in den letzten Jahren sehr modern waren.
Lose Kopplung ist ein objektorientiertes Prinzip, bei dem jede Komponente des Systems ihre Verantwortung kennt und sich nicht um die anderen Komponenten kümmert (oder zumindest versucht, sich nicht so sehr wie möglich um sie zu kümmern). Eine lose Kopplung ist eine gute Sache, da Sie die verschiedenen Module problemlos wiederverwenden können. Sie sind nicht mit den Schnittstellen anderer Module gekoppelt. Wenn Sie Publish / Subscribe verwenden, sind Sie nur mit der Publish / Subscribe-Oberfläche verbunden, was keine große Sache ist - nur zwei Methoden. Wenn Sie sich also entscheiden, ein Modul in einem anderen Projekt wiederzuverwenden, können Sie es einfach kopieren und einfügen, und es wird wahrscheinlich funktionieren, oder Sie werden zumindest nicht viel Aufwand benötigen, um es zum Laufen zu bringen.
Wenn wir über lose Kopplung sprechen, sollten wir die Trennung von Bedenken erwähnen. Wenn Sie eine Anwendung mit einem MV * -Architekturmuster erstellen, haben Sie immer ein Modell und eine Ansicht. Das Modell ist der geschäftliche Teil der Anwendung. Sie können es in verschiedenen Anwendungen wiederverwenden. Daher ist es keine gute Idee, es mit der Ansicht einer einzelnen Anwendung zu koppeln, in der Sie es anzeigen möchten, da Sie normalerweise in den verschiedenen Anwendungen unterschiedliche Ansichten haben. Es ist daher eine gute Idee, Publish / Subscribe für die Model-View-Kommunikation zu verwenden. Wenn Ihr Modell ändert, wird ein Ereignis veröffentlicht, die Ansicht fängt es ab und aktualisiert sich selbst. Sie haben keinen Overhead durch das Veröffentlichen / Abonnieren, es hilft Ihnen bei der Entkopplung. Auf die gleiche Weise können Sie beispielsweise Ihre Anwendungslogik im Controller behalten (MVVM, MVP ist nicht gerade ein Controller) und die Ansicht so einfach wie möglich halten. Wenn sich Ihre Ansicht ändert (oder der Benutzer beispielsweise auf etwas klickt), wird nur ein neues Ereignis veröffentlicht, der Controller fängt es ab und entscheidet, was zu tun ist. Wenn Sie mit dem vertraut sindMVC- Muster oder mit MVVM in Microsoft-Technologien (WPF / Silverlight) können Sie sich das Publish / Subscribe wie das Observer-Muster vorstellen . Dieser Ansatz wird in Frameworks wie Backbone.js, Knockout.js (MVVM) verwendet.
Hier ist ein Beispiel:
//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);
}
}
}());
Ein anderes Beispiel. Wenn Ihnen der MV * -Ansatz nicht gefällt, können Sie etwas anderes verwenden (es gibt einen Schnittpunkt zwischen dem, den ich als nächstes beschreibe, und dem zuletzt genannten). Strukturieren Sie Ihre Anwendung einfach in verschiedene Module. Schauen Sie sich zum Beispiel Twitter an.
Wenn Sie sich die Benutzeroberfläche ansehen, haben Sie einfach verschiedene Felder. Sie können sich jede Box als ein anderes Modul vorstellen. Zum Beispiel können Sie einen Tweet posten. Diese Aktion erfordert die Aktualisierung einiger Module. Zunächst müssen Ihre Profildaten aktualisiert werden (Feld oben links), aber auch Ihre Zeitachse. Natürlich können Sie Verweise auf beide Module beibehalten und sie separat über ihre öffentliche Oberfläche aktualisieren, aber es ist einfacher (und besser), nur ein Ereignis zu veröffentlichen. Dies erleichtert die Änderung Ihrer Anwendung aufgrund der lockeren Kopplung. Wenn Sie ein neues Modul entwickeln, das von neuen Tweets abhängt, können Sie einfach das Ereignis "Publish-Tweet" abonnieren und damit umgehen. Dieser Ansatz ist sehr nützlich und kann Ihre Anwendung sehr entkoppelt machen. Sie können Ihre Module sehr einfach wiederverwenden.
Hier ist ein grundlegendes Beispiel für den letzten Ansatz (dies ist kein originaler Twitter-Code, sondern nur ein Beispiel von mir):
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);
});
}
};
}());
Für diesen Ansatz gibt es einen ausgezeichneten Vortrag von Nicholas Zakas . Für den MV * -Ansatz werden die besten mir bekannten Artikel und Bücher von Addy Osmani veröffentlicht .
Nachteile: Sie müssen vorsichtig sein, wenn Publish / Subscribe übermäßig verwendet wird. Wenn Sie Hunderte von Ereignissen haben, kann es sehr verwirrend werden, alle zu verwalten. Es kann auch zu Kollisionen kommen, wenn Sie den Namespace nicht verwenden (oder ihn nicht richtig verwenden). Eine erweiterte Implementierung von Mediator, die einem Publish / Subscribe ähnelt, finden Sie hier https://github.com/ajacksified/Mediator.js . Es hat einen Namespace und Funktionen wie "Bubbling", die natürlich unterbrochen werden können. Ein weiterer Nachteil von Publish / Subscribe ist das Testen harter Einheiten. Es kann schwierig werden, die verschiedenen Funktionen in den Modulen zu isolieren und unabhängig voneinander zu testen.
Das Hauptziel besteht darin, die Kopplung zwischen den Codes zu verringern. Es ist eine etwas ereignisbasierte Denkweise, aber die "Ereignisse" sind nicht an ein bestimmtes Objekt gebunden.
Ich werde unten ein großes Beispiel in einem Pseudocode schreiben, der ein bisschen wie JavaScript aussieht.
Nehmen wir an, wir haben ein Klassenradio und ein Klassenrelais:
Immer wenn das Radio ein Signal empfängt, möchten wir, dass mehrere Relais die Nachricht auf irgendeine Weise weiterleiten. Die Anzahl und Art der Relais kann unterschiedlich sein. Wir könnten es so machen:
Das funktioniert gut. Stellen Sie sich nun vor, wir möchten, dass eine andere Komponente auch an den Signalen teilnimmt, die die Radio-Klasse empfängt, nämlich Lautsprecher:
(Entschuldigung, wenn die Analogien nicht erstklassig sind ...)
Wir könnten das Muster noch einmal wiederholen:
Wir könnten dies noch verbessern, indem wir eine Schnittstelle wie "SignalListener" erstellen, sodass wir nur eine Liste in der Radio-Klasse benötigen und immer dieselbe Funktion für jedes Objekt aufrufen können, das das Signal abhören möchte. Dies schafft jedoch immer noch eine Kopplung zwischen der Schnittstelle / Basisklasse / usw., für die wir uns entscheiden, und der Radio-Klasse. Grundsätzlich müssen Sie bei jedem Wechsel einer Radio-, Signal- oder Relay-Klasse darüber nachdenken, wie sich dies möglicherweise auf die beiden anderen Klassen auswirken könnte.
Versuchen wir jetzt etwas anderes. Erstellen wir eine vierte Klasse mit dem Namen RadioMast:
Jetzt haben wir ein Muster , das uns bekannt ist, und wir können es für eine beliebige Anzahl und Art von Klassen verwenden, solange sie:
Also ändern wir die Radio-Klasse in ihre endgültige, einfache Form:
Und wir fügen die Lautsprecher und das Relais zur Empfängerliste des RadioMast für diese Art von Signal hinzu:
Jetzt hat die Speakers and Relay-Klasse keine Kenntnis von irgendetwas, außer dass sie über eine Methode verfügt, die ein Signal empfangen kann, und die Radio-Klasse als Herausgeber ist sich des RadioMast bewusst, an den sie Signale veröffentlicht. Dies ist der Punkt, an dem ein Nachrichtenübermittlungssystem wie Publish / Subscribe verwendet wird.
quelle
class
Schlüsselwort nicht. Bitte betonen Sie diese Tatsache, z. indem Sie Ihren Code als Pseudocode klassifizieren.Die anderen Antworten haben großartige Arbeit geleistet, um zu zeigen, wie das Muster funktioniert. Ich wollte die implizite Frage " Was ist los mit dem alten Weg?" " Ansprechen, da ich kürzlich mit diesem Muster gearbeitet habe, und ich finde, dass es eine Veränderung in meinem Denken beinhaltet.
Stellen Sie sich vor, wir haben ein Wirtschaftsbulletin abonniert. Das Bulletin veröffentlicht eine Überschrift: " Senken Sie den Dow Jones um 200 Punkte ". Das wäre eine seltsame und etwas verantwortungslose Nachricht. Wenn jedoch veröffentlicht wurde: " Enron hat heute Morgen einen Insolvenzschutz nach Kapitel 11 beantragt ", ist dies eine nützlichere Nachricht. Beachten Sie, dass die Nachricht verursachen kann der Dow Jones um 200 Punkte fällt, aber das ist eine andere Sache.
Es gibt einen Unterschied zwischen dem Senden eines Befehls und dem Hinweis auf etwas, das gerade passiert ist. Nehmen Sie vor diesem Hintergrund Ihre Originalversion des Pub / Sub-Musters und ignorieren Sie den Handler vorerst:
Hier besteht bereits eine implizite starke Kopplung zwischen der Benutzeraktion (ein Klick) und der Systemantwort (eine Bestellung wird entfernt). In Ihrem Beispiel gibt die Aktion effektiv einen Befehl aus. Betrachten Sie diese Version:
Jetzt reagiert der Handler auf etwas Interessantes, das passiert ist, ist jedoch nicht verpflichtet, eine Bestellung zu entfernen. Tatsächlich kann der Handler alle möglichen Dinge tun, die nicht direkt mit dem Entfernen eines Auftrags zusammenhängen, aber dennoch möglicherweise für die aufrufende Aktion relevant sind. Beispielsweise:
Die Unterscheidung zwischen einem Befehl und einer Benachrichtigung ist eine nützliche Unterscheidung mit diesem Muster, IMO.
quelle
remindUserToFloss
&increaseProgrammerBrowniePoints
) in separaten Modulen befinden würden, würden Sie zwei Ereignisse direkt nacheinander direkt dort veröffentlichenhandleRemoveOrderRequest
oder hätten SieflossModule
ein Ereignis in einembrowniePoints
Modul veröffentlicht, wennremindUserToFloss()
dies abgeschlossen ist?Damit Sie keine Methoden- / Funktionsaufrufe fest codieren müssen, veröffentlichen Sie das Ereignis einfach, ohne sich darum zu kümmern, wer zuhört. Dies macht den Publisher unabhängig vom Abonnenten und reduziert die Abhängigkeit (oder Kopplung, egal welchen Begriff Sie bevorzugen) zwischen zwei verschiedenen Teilen der Anwendung.
Hier sind einige Nachteile der Kopplung, wie in Wikipedia erwähnt
Stellen Sie sich so etwas wie ein Objekt vor, das Geschäftsdaten kapselt. Es hat einen fest codierten Methodenaufruf, um die Seite zu aktualisieren, wenn das Alter festgelegt wird:
Jetzt kann ich das Personenobjekt nicht testen, ohne auch das einzuschließen
showAge
Funktion einzuschließen. Wenn ich das Alter auch in einem anderen GUI-Modul anzeigen muss, muss ich diesen Methodenaufruf fest codieren.setAge
, und jetzt gibt es Abhängigkeiten für zwei nicht verwandte Module im person-Objekt. Es ist auch nur schwer zu pflegen, wenn Sie sehen, dass diese Anrufe getätigt werden und sie sich nicht einmal in derselben Datei befinden.Beachten Sie, dass Sie innerhalb desselben Moduls natürlich direkte Methodenaufrufe haben können. Geschäftsdaten und oberflächliches GUI-Verhalten sollten sich jedoch nach vernünftigen Maßstäben nicht im selben Modul befinden.
quelle
removeOrder
überhaupt existiert, sodass Sie nicht davon abhängig sein können. Im zweiten Beispiel müssen Sie wissen.Die Implementierung von PubSub wird häufig dort gesehen, wo -
Beispielcode -
quelle
Das Papier "Die vielen Gesichter des Publizierens / Abonnierens" ist eine gute Lektüre und eine Sache, die sie hervorheben, ist die Entkopplung in drei "Dimensionen". Hier ist meine grobe Zusammenfassung, aber bitte beziehen Sie sich auch auf das Papier.
quelle
Einfache Antwort Die ursprüngliche Frage suchte nach einer einfachen Antwort. Hier ist mein Versuch.
Javascript bietet keinen Mechanismus für Codeobjekte, um ihre eigenen Ereignisse zu erstellen. Sie benötigen also eine Art Ereignismechanismus. Das Publish / Subscribe-Muster erfüllt diese Anforderungen, und Sie müssen einen Mechanismus auswählen, der Ihren eigenen Anforderungen am besten entspricht.
Jetzt sehen wir einen Bedarf für das Pub / Sub-Muster. Müssen Sie dann lieber DOM-Ereignisse anders behandeln als Ihre Pub / Sub-Ereignisse? Um die Komplexität und andere Konzepte wie die Trennung von Bedenken (SoC) zu reduzieren, sehen Sie möglicherweise den Vorteil, dass alles einheitlich ist.
Paradoxerweise führt mehr Code zu einer besseren Trennung von Bedenken, was sich gut auf sehr komplexe Webseiten skalieren lässt.
Ich hoffe, jemand findet dies eine ausreichend gute Diskussion, ohne ins Detail zu gehen.
quelle