JavaScript Design Patterns: Das Observer Pattern

In JavaScript gibt es ein Problem, das häufig auftritt. Sie benötigen eine Möglichkeit, Teile einer Seite als Reaktion auf bestimmte Ereignisse mit den von diesen bereitgestellten Daten zu aktualisieren. Nehmen wir zum Beispiel Benutzereingaben, die Sie dann in eine oder mehrere Komponenten projizieren. Dies führt zu viel Push-and-Pull im Code, um alles synchron zu halten.

Hier kann das Observer-Entwurfsmuster helfen – es ermöglicht die Eins-zu-Viele-Datenbindung zwischen Elementen. Diese Einwegdatenbindung kann ereignisgesteuert sein. Mit diesem Muster können Sie wiederverwendbaren Code erstellen, der für Ihre spezifischen Anforderungen geeignet ist.

In diesem Artikel möchte ich das Observer-Entwurfsmuster untersuchen. Es wird Ihnen helfen, ein häufiges Problem zu lösen, das Sie beim clientseitigen Scripting sehen. Dies ist eine One-to-Many-, One-Way- und ereignisgesteuerte Datenbindung. Es ist ein Problem, das häufig auftritt, wenn Sie viele Elemente haben, die synchron sein müssen.

Ich werde ECMAScript 6 verwenden, um das Muster zu veranschaulichen. Ja, es wird Klassen, Pfeilfunktionen und Konstanten geben. Fühlen Sie sich frei, diese Themen auf eigene Faust zu erkunden, wenn Sie nicht bereits vertraut sind. Ich werde Teile von ES6 verwenden, die nur syntaktischen Zucker einführen, so dass es bei Bedarf mit ES5 portierbar ist.

Und ich werde Test-Driven-Development (TDD) verwenden, um an dem Muster zu arbeiten. Auf diese Weise haben Sie eine Möglichkeit zu wissen, wie jede Komponente nützlich ist.

Die neuen Sprachfunktionen in ES6 sorgen für prägnanten Code. Also, lass uns anfangen.

Der Ereignisbeobachter

Eine allgemeine Ansicht des Musters sieht folgendermaßen aus:

EventObserver│ ├── subscribe: adds new observable events│ ├── unsubscribe: removes observable events|└── broadcast: executes all events with bound data

Nachdem ich das Beobachtermuster ausgearbeitet habe, füge ich eine Wortzahl hinzu, die es verwendet. Die Wortzahl-Komponente nimmt diesen Beobachter und bringt alles zusammen.

Zum Initialisieren der EventObserver tun:

class EventObserver { constructor() { this.observers = ; }}

Beginnen Sie mit einer leeren Liste der beobachteten Ereignisse und tun Sie dies für jede neue Instanz. Von nun an fügen wir weitere Methoden in EventObserver hinzu, um das Entwurfsmuster zu konkretisieren.

Die Subscribe-Methode

Zum Hinzufügen neuer Ereignisse:

subscribe(fn) { this.observers.push(fn);}

Greifen Sie auf die Liste der beobachteten Ereignisse zu und schieben Sie ein neues Element in das Array. Die Liste der Ereignisse ist eine Liste von Callback-Funktionen.

Eine Möglichkeit, diese Methode in einfachem JavaScript zu testen, ist wie folgt:

// Arrangeconst observer = new EventObserver();const fn = () => {};// Actobserver.subscribe(fn);// Assertassert.strictEqual(observer.observers.length, 1);

Ich verwende Knotenzusicherungen, um diese Komponente im Knoten zu testen. Es gibt genau die gleichen Behauptungen wie Chai-Behauptungen.

Hinweis Die Liste der beobachteten Ereignisse besteht aus mehreren Rückrufen. Wir überprüfen dann die Länge der Liste und behaupten, dass sich der Rückruf in der Liste befindet.

Die Unsubscribe-Methode

Zum Entfernen von Ereignissen:

unsubscribe(fn) { this.observers = this.observers.filter((subscriber) => subscriber !== fn);}

Filtern Sie aus der Liste alles heraus, was der Rückruffunktion entspricht. Wenn keine Übereinstimmung vorliegt, bleibt der Rückruf in der Liste. Der Filter gibt eine neue Liste zurück und weist die Liste der Beobachter neu zu.

Um diese nette Methode zu testen, tun:

// Arrangeconst observer = new EventObserver();const fn = () => {};observer.subscribe(fn);// Actobserver.unsubscribe(fn);// Assertassert.strictEqual(observer.observers.length, 0);

Der Rückruf muss mit derselben Funktion in der Liste übereinstimmen. Wenn es eine Übereinstimmung gibt, entfernt die Unsubscribe-Methode sie aus der Liste. Hinweis Der Test verwendet die Funktionsreferenz, um sie hinzuzufügen und zu entfernen.

Die Broadcast-Methode

, um alle Ereignisse aufzurufen:

broadcast(data) { this.observers.forEach((subscriber) => subscriber(data));}

Dies durchläuft die Liste der beobachteten Ereignisse und führt alle Rückrufe aus. Damit erhalten Sie die notwendige Eins-zu-Viele-Beziehung zu den abonnierten Ereignissen. Sie übergeben den Parameter data, der die Rückrufdaten bindet.

ES6 macht den Code mit einer Pfeilfunktion effektiver. Beachten Sie die Funktion (subscriber) => subscriber(data), die den größten Teil der Arbeit erledigt. Diese einzeilige Pfeilfunktion profitiert von dieser kurzen ES6-Syntax. Dies ist eine deutliche Verbesserung in der Programmiersprache JavaScript.

Um diese Broadcast-Methode zu testen, führen Sie:

// Arrangeconst observer = new EventObserver();let subscriberHasBeenCalled = false;const fn = (data) => subscriberHasBeenCalled = data;observer.subscribe(fn);// Actobserver.broadcast(true);// Assertassert(subscriberHasBeenCalled);

Verwenden Sie let anstelle von const, damit wir den Wert der Variablen ändern können. Dadurch wird die Variable veränderbar, sodass ich ihren Wert innerhalb des Rückrufs neu zuweisen kann. Die Verwendung eines let in Ihrem Code sendet ein Signal an andere Programmierer, dass sich die Variable irgendwann ändert. Dies erhöht die Lesbarkeit und Klarheit Ihres JavaScript-Codes.

Dieser Test gibt mir das nötige Vertrauen, um sicherzustellen, dass der Observer wie erwartet funktioniert. Bei TDD geht es darum, wiederverwendbaren Code in einfachem JavaScript zu erstellen. Es gibt Vorteile, testbaren Code in einfachem JavaScript zu schreiben. Testen Sie alles und behalten Sie bei, was für die Wiederverwendung von Code gut ist.

Damit haben wir die EventObserver konkretisiert. Die Frage ist, was können Sie damit bauen?

Das Observer-Muster in Aktion: Eine Blog-Wortzahl-Demo

Für die Demo ist es an der Zeit, einen Blog-Beitrag zu erstellen, in dem die Wortzahl für Sie gespeichert wird. Jeder Tastendruck, den Sie als Eingabe eingeben, wird durch das Observer-Entwurfsmuster synchronisiert. Betrachten Sie es als freie Texteingabe, bei der jedes Ereignis ein Update dorthin auslöst, wo Sie es benötigen.

Um eine Wortzahl aus der freien Texteingabe zu erhalten, kann man Folgendes tun:

const getWordCount = (text) => text ? text.trim().split(/\s+/).length : 0;

Fertig! In dieser scheinbar einfachen reinen Funktion ist viel los, wie wäre es also mit einem bescheidenen Komponententest? Auf diese Weise ist klar, was ich damit vorhatte:

// Arrangeconst blogPost = 'This is a blog \n\n post with a word count. ';// Actconst count = getWordCount(blogPost);// Assertassert.strictEqual(count, 9);

Beachten Sie die etwas verrückte Eingabezeichenfolge in blogPost . Ich beabsichtige, dass diese Funktion so viele Randfälle wie möglich abdeckt. Solange es mir eine richtige Wortzahl gibt, bewegen wir uns tatsächlich in die richtige Richtung.

Als Randnotiz ist dies die wahre Kraft von TDD. Man kann diese Implementierung iterieren und so viele Anwendungsfälle wie möglich abdecken. Der Komponententest sagt Ihnen, wie ich erwarte, dass sich dies verhält. Wenn das Verhalten aus irgendeinem Grund einen Fehler aufweist, ist es einfach, es zu iterieren und zu optimieren. Mit dem Test gibt es genug Beweise für jede andere Person, um Änderungen vorzunehmen.

Zeit, diese wiederverwendbaren Komponenten mit dem DOM zu verbinden. Dies ist der Teil, in dem Sie einfaches JavaScript verwenden und es direkt in den Browser schweißen können.

Eine Möglichkeit, dies zu tun, besteht darin, den folgenden HTML-Code auf der Seite zu haben:

<textarea placeholder="Enter your blog post..." class="blogPost"></textarea>

Gefolgt von diesem JavaScript:

const wordCountElement = document.createElement('p');wordCountElement.className = 'wordCount';wordCountElement.innerHTML = 'Word Count: <strong>0</strong>';document.body.appendChild(wordCountElement);const blogObserver = new EventObserver();blogObserver.subscribe((text) => { const blogCount = document.getElementById('blogWordCount'); blogCount.textContent = getWordCount(text);});const blogPost = document.getElementById('blogPost');blogPost.addEventListener('keyup', () => blogObserver.broadcast(blogPost.value));

Nehmen Sie Ihren gesamten wiederverwendbaren Code und setzen Sie das Observer-Entwurfsmuster ein. Dadurch werden Änderungen im Textbereich verfolgt und Sie erhalten eine Wortzahl direkt darunter. Ich verwende body.appendChild() in der DOM-API, um dieses neue Element hinzuzufügen. Fügen Sie dann die Ereignis-Listener hinzu, um sie zum Leben zu erwecken.

Hinweis Mit Pfeilfunktionen ist es möglich, einzeilige Ereignisse zu verdrahten. Tatsächlich übertragen Sie damit ereignisgesteuerte Änderungen an alle Abonnenten. Der () => blogObserver.broadcast() erledigt hier den Großteil der Arbeit. Es werden sogar die neuesten Änderungen am Textbereich direkt an die Rückruffunktion übergeben. Ja, clientseitiges Scripting ist super cool.

Keine Demo ist komplett ohne eine, die Sie berühren und optimieren können, unten ist der CodePen:

Siehe den Stift Das Observer-Muster von SitePoint (@SitePoint) auf CodePen.

Nun, ich würde diese Funktion nicht als vollständig bezeichnen. Es ist nur ein Ausgangspunkt des Beobachter-Entwurfsmusters. Die Frage in meinem Kopf ist, wie weit bist du bereit zu gehen?

Mit Blick auf die Zukunft

Es liegt an Ihnen, diese Idee noch weiter voranzutreiben. Es gibt viele Möglichkeiten, das Observer-Entwurfsmuster zum Erstellen neuer Features zu verwenden.

Sie können die Demo mit:

  • Eine weitere Komponente, die die Anzahl der Absätze zählt
  • Eine weitere Komponente, die eine Vorschau des eingegebenen Textes anzeigt
  • Verbessern Sie die Vorschau mit Markdown-Unterstützung, z. B.

Dies sind nur einige Ideen, die Sie tun können, um tiefer in dieses Thema einzutauchen. Die obigen Verbesserungen werden Ihre Programmierkenntnisse herausfordern.

Fazit

Das Observer-Entwurfsmuster kann Ihnen helfen, reale Probleme in JavaScript zu lösen. Dies löst das ewige Problem, eine Reihe von Elementen mit denselben Daten zu synchronisieren. Wie so oft, wenn der Browser bestimmte Ereignisse auslöst. Ich bin mir sicher, dass die meisten von Ihnen inzwischen auf ein solches Problem gestoßen sind und auf Tools und Abhängigkeiten von Drittanbietern gestoßen sind.

Mit diesem Designmuster können Sie so weit gehen, wie es Ihre Vorstellungskraft zulässt. In der Programmierung abstrahieren Sie die Lösung in ein Muster und erstellen wiederverwendbaren Code. Es gibt keine Begrenzung, wie weit dies Sie bringen wird.

Ich hoffe, Sie sehen, wie viel Sie mit ein wenig Disziplin und Mühe in einfachem JavaScript tun können. Die neuen Funktionen in der Sprache, wie ES6, helfen Ihnen, prägnanten Code zu schreiben, der wiederverwendbar ist.

Dieser Artikel wurde von Giulio Mainardi begutachtet. Vielen Dank an alle Peer-Reviewer von SitePoint, die SitePoint-Inhalte so gut wie möglich gemacht haben!

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.