Designmuster in JavaScript verstehen

Heute werden wir unsere IT-Hüte anziehen, wenn wir einige gängige Designmuster kennenlernen. Entwurfsmuster bieten Entwicklern die Möglichkeit, technische Probleme auf wiederverwendbare und elegante Weise zu lösen. Möchten Sie ein besserer JavaScript-Entwickler werden? Dann lies weiter.

Erneut veröffentlichtes Tutorial

Alle paar Wochen besuchen wir einige der Lieblingsbeiträge unserer Leser aus der gesamten Geschichte der Website. Dieses Tutorial wurde erstmals im Juli 2012 veröffentlicht.


Einführung

Feste Entwurfsmuster sind der Grundbaustein für wartungsfähige Softwareanwendungen. Wenn Sie jemals an einem technischen Interview teilgenommen haben, wurden Sie gerne danach gefragt. In diesem Tutorial werfen wir einen Blick auf einige Muster, die Sie heute verwenden können.


Was ist ein Designmuster??

Ein Entwurfsmuster ist eine wiederverwendbare Softwarelösung

Einfach ausgedrückt, ist ein Entwurfsmuster eine wiederverwendbare Softwarelösung für einen bestimmten Problemtyp, der häufig bei der Entwicklung von Software auftritt. Im Laufe der langjährigen Praxis in der Softwareentwicklung haben Experten Wege gefunden, ähnliche Probleme zu lösen. Diese Lösungen wurden in Designmuster gekapselt. So:

  • Muster sind bewährte Lösungen für Softwareentwicklungsprobleme
  • Muster sind skalierbar, da sie normalerweise strukturiert sind und Regeln enthalten, die Sie befolgen sollten
  • Muster sind für ähnliche Probleme wiederverwendbar

Weitere Beispiele für Entwurfsmuster finden Sie im Tutorial.


Arten von Design Patterns

In der Softwareentwicklung werden Entwurfsmuster im Allgemeinen in einige Kategorien unterteilt. Wir werden die drei wichtigsten in diesem Tutorial behandeln. Sie werden im Folgenden kurz erklärt:

  1. Kreativ Muster konzentrieren sich auf Möglichkeiten, Objekte oder Klassen zu erstellen. Dies mag einfach klingen (und ist es in manchen Fällen auch), aber große Anwendungen müssen den Objekterstellungsprozess steuern.
  2. Strukturelle Entwurfsmuster konzentrieren sich auf die Verwaltung von Beziehungen zwischen Objekten, sodass Ihre Anwendung skalierbar gestaltet wird. Ein wichtiger Aspekt von Strukturmustern ist es sicherzustellen, dass eine Änderung in einem Teil Ihrer Anwendung nicht alle anderen Teile beeinflusst.
  3. Verhalten Muster konzentrieren sich auf die Kommunikation zwischen Objekten.

Nach dem Lesen dieser kurzen Beschreibungen haben Sie möglicherweise noch Fragen. Das ist natürlich und die Dinge werden sich klären, wenn wir uns unten einige Designmuster genauer ansehen. Lesen Sie weiter!


Ein Hinweis zu Klassen in JavaScript

Beim Lesen von Entwurfsmustern werden häufig Verweise auf Klassen und Objekte angezeigt. Dies kann verwirrend sein, da JavaScript nicht wirklich das Konstrukt "class" hat. ein korrekterer Begriff ist "Datentyp".

Datentypen in JavaScript

JavaScript ist eine objektorientierte Sprache, in der Objekte von anderen Objekten in einem Konzept erben, das als prototypische Vererbung bezeichnet wird. Ein Datentyp kann erstellt werden, indem eine so genannte Konstruktorfunktion definiert wird:

Funktion Person (config) this.name = config.name; this.age = config.age;  Person.prototype.getAge = function () return this.age; ; var tilo = neue Person (Name: "Tilo", Alter: 23); console.log (tilo.getAge ());

Beachten Sie die Verwendung der Prototyp bei der Definition von Methoden auf der Person Datentyp. Da mehrere Person Objekte referenzieren denselben Prototyp, dies ermöglicht das getAge () Methode, die von allen Instanzen des Person Datentyp, anstatt es für jede Instanz neu zu definieren. Darüber hinaus jeder Datentyp, der von erbt Person habe Zugriff auf die getAge () Methode.

Umgang mit dem Datenschutz

Ein weiteres häufiges Problem in JavaScript ist, dass private Variablen nicht wirklich wahrgenommen werden. Wir können jedoch Verschlüsse verwenden, um die Privatsphäre etwas zu simulieren. Betrachten Sie den folgenden Ausschnitt:

var retinaMacbook = (function () // Private Variablen var RAM, addRAM; RAM = 4; // Private Methode addRAM = Funktion (additionalRAM) RAM + = additionalRAM;; return // Öffentliche Variablen und Methoden USB: undefined , insertUSB: function (device) this.USB = device;, removeUSB: function () var device = this.USB; this.USB = undefined; Gerät zurückgeben;;) ();

Im obigen Beispiel haben wir eine erstellt retinaMacbook Objekt, mit öffentlichen und privaten Variablen und Methoden. So würden wir es verwenden:

retinaMacbook.insertUSB ("myUSB"); console.log (retinaMacbook.USB); // Abmelden von "myUSB" console.log (retinaMacbook.RAM) // Abmelden undefiniert

Mit Funktionen und Schließungen in JavaScript können wir noch viel mehr tun, aber wir werden in diesem Tutorial nicht auf alles eingehen. Nachdem wir diese kleine Lektion über JavaScript-Datentypen und den Datenschutz hinter uns gelassen haben, können wir Sie mitnehmen, um Designmuster zu lernen.


Kreationelle Designmuster

Es gibt viele verschiedene Arten von kreativen Designmustern, aber wir werden in diesem Tutorial zwei davon behandeln: Builder und Prototype. Ich finde, dass diese oft genug verwendet werden, um die Aufmerksamkeit zu rechtfertigen.

Generator-Muster

Das Builder-Muster wird häufig in der Webentwicklung verwendet, und Sie haben es wahrscheinlich schon vorher verwendet, ohne es zu merken. Einfach ausgedrückt, kann dieses Muster wie folgt definiert werden:

Durch das Anwenden des Builder-Musters können Objekte erstellt werden, indem nur der Typ und der Inhalt des Objekts angegeben werden. Wir müssen das Objekt nicht explizit erstellen.

Zum Beispiel haben Sie dies wahrscheinlich unzählige Male in jQuery gemacht:

var myDiv = $ ('
Das ist ein div.
'); // myDiv stellt jetzt ein jQuery-Objekt dar, das auf einen DOM-Knoten verweist. var someText = $ ('

'); // someText ist ein jQuery-Objekt, das auf ein HTMLParagraphElement verweist. var input = $ ('');

Schauen Sie sich die drei Beispiele oben an. In der ersten bestanden wir eine

Element mit etwas Inhalt. Im zweiten passierten wir einen leeren Platz

Etikett. In der letzten haben wir einen Element. Das Ergebnis aller drei war das gleiche: Es wurde ein jQuery-Objekt zurückgegeben, das auf einen DOM-Knoten verweist.

Das $ Variable nimmt das Builder-Pattern in jQuery an. In jedem Beispiel wurde uns ein jQuery-DOM-Objekt zurückgegeben, und wir hatten Zugriff auf alle von der jQuery-Bibliothek bereitgestellten Methoden, aber zu keinem Zeitpunkt haben wir explizit aufgerufen document.createElement. Die JS-Bibliothek hat all das unter der Haube erledigt.

Stellen Sie sich vor, wie viel Arbeit es wäre, wenn wir das DOM-Element explizit erstellen und Inhalte einfügen müssten! Durch die Nutzung des Builder-Musters können wir uns auf den Typ und den Inhalt des Objekts konzentrieren, anstatt es explizit zu erstellen.

Prototypmuster

Zuvor haben wir durch Definition von Datentypen in JavaScript durch Funktionen und Hinzufügen von Methoden zu den Objekten des Objekts gesprochen Prototyp. Das Prototypmuster ermöglicht, dass Objekte über ihre Prototypen von anderen Objekten erben können.

Das Prototypmuster ist ein Muster, bei dem Objekte basierend auf einer Vorlage eines vorhandenen Objekts durch Klonen erstellt werden.

Dies ist eine einfache und natürliche Möglichkeit, Vererbung in JavaScript zu implementieren. Zum Beispiel:

var Person = numFeet: 2, numHeads: 1, numHands: 2; //Object.create verwendet sein erstes Argument und wendet es auf den Prototyp Ihres neuen Objekts an. var tilo = Object.create (Person); console.log (tilo.numHeads); // gibt 1 aus tilo.numHeads = 2; console.log (tilo.numHeads) // Ausgaben 2

Die Eigenschaften (und Methoden) im Person Objekt wird auf den Prototyp der Version angewendet Tilo Objekt. Wir können die Eigenschaften auf dem neu definieren Tilo Objekt, wenn wir wollen, dass sie anders sind.

Im obigen Beispiel haben wir verwendet Object.create (). Internet Explorer 8 unterstützt jedoch nicht die neuere Methode. In diesen Fällen können wir das Verhalten simulieren:

var vehiclePrototype = init: function (carModel) this.model = carModel; , getModel: function () console.log ("Das Modell dieses Fahrzeugs ist" + this.model); ; Funktionsfahrzeug (Modell) Funktion F () ; F.prototype = vehiclePrototype; var f = new F (); f.init (Modell); return f;  var car = Fahrzeug ("Ford Escort"); car.getModel ();

Der einzige Nachteil dieser Methode besteht darin, dass Sie keine schreibgeschützten Eigenschaften angeben können, die bei der Verwendung angegeben werden können Object.create (). Nichtsdestotrotz zeigt das Prototypmuster, wie Objekte von anderen Objekten erben können.


Strukturelle Entwurfsmuster

Strukturelle Entwurfsmuster sind wirklich hilfreich, um herauszufinden, wie ein System funktionieren sollte. Sie ermöglichen unseren Anwendungen eine einfache Skalierbarkeit und Wartungsfreundlichkeit. Wir werden uns die folgenden Muster in dieser Gruppe ansehen: Composite und Facade.

Zusammengesetztes Muster

Das zusammengesetzte Muster ist ein anderes Muster, das Sie wahrscheinlich zuvor ohne weitere Verwendung verwendet haben.

Das zusammengesetzte Muster besagt, dass eine Gruppe von Objekten auf dieselbe Weise wie ein einzelnes Objekt der Gruppe behandelt werden kann.

Was bedeutet das? Betrachten Sie dieses Beispiel in jQuery (die meisten JS-Bibliotheken haben ein Äquivalent dazu):

$ ('. myList'). addClass ('selected'); $ ('# myItem'). addClass ('selected'); // Tun Sie dies nicht für große Tabellen, es ist nur ein Beispiel. $ ("# dataTable tbody tr"). on ("click", function (event) alert ($ (this) .text ());); $ ('# myButton'). on ("click", Funktion (Ereignis) alert ("Clicked."););

Die meisten JavaScript-Bibliotheken bieten eine konsistente API, unabhängig davon, ob es sich um ein einzelnes DOM-Element oder um ein Array von DOM-Elementen handelt. Im ersten Beispiel können wir das hinzufügen ausgewählt Klasse für alle von der .meine Liste Selector, aber wir können dieselbe Methode verwenden, wenn Sie ein einzelnes DOM-Element verwenden, #myItem. In ähnlicher Weise können wir Event-Handler mithilfe von verknüpfen auf() Methode auf mehreren Knoten oder auf einem einzelnen Knoten über dieselbe API.

Durch die Nutzung des Composite-Musters erhalten Sie mit jQuery (und vielen anderen Bibliotheken) eine vereinfachte API.

Das zusammengesetzte Muster kann manchmal auch Probleme verursachen. In einer locker typisierten Sprache wie JavaScript kann es oft hilfreich sein, zu wissen, ob es sich um ein einzelnes Element oder um mehrere Elemente handelt. Da das zusammengesetzte Muster für beide die gleiche API verwendet, können wir oft eine für die andere verwechseln und unerwartete Fehler verursachen. Einige Bibliotheken wie YUI3 bieten zwei verschiedene Methoden zum Abrufen von Elementen (Y.one () vs Ihr()).

Fassadenmuster

Hier ist ein weiteres gemeinsames Muster, das wir als selbstverständlich betrachten. In der Tat ist dies einer meiner Favoriten, weil es einfach ist, und ich habe gesehen, dass es überall verwendet wird, um bei Inkonsistenzen des Browsers zu helfen. Hier ist, worum es beim Fassadenmuster geht:

Das Fassadenmuster bietet dem Benutzer eine einfache Benutzeroberfläche, während die darunter liegende Komplexität ausgeblendet wird.

Das Fassadenmuster verbessert fast immer die Benutzerfreundlichkeit einer Software. Wieder mit jQuery als Beispiel, ist die populärste Methode der Bibliothek die bereit() Methode:

$ (document) .ready (function () // Ihr ganzer Code geht hier hin ...);

Das bereit() Methode implementiert tatsächlich eine Fassade. Wenn Sie sich die Quelle ansehen, finden Sie Folgendes:

ready: (function () … // Mozilla, Opera und Webkit if (document.addEventListener) document.addEventListener ("DOMContentLoaded", idempotent_fn, false);… // IE-Ereignismodell else if (document.attachEvent) // das Auslösen vor Onload sicherstellen; möglicherweise zu spät, aber auch sicher für iframes document.attachEvent ("onreadystatechange", idempotent_fn); // Ein Fallback auf window.onload, das immer funktioniert window.attachEvent ("onload", idempotent_fn); …)

Unter der Haube die bereit() Methode ist nicht so einfach. jQuery normalisiert die Browserinkonsistenzen, um dies sicherzustellen bereit() wird zur richtigen Zeit abgefeuert. Als Entwickler wird Ihnen jedoch eine einfache Benutzeroberfläche angezeigt.

Die meisten Beispiele des Fassadenmusters folgen diesem Prinzip. Bei der Implementierung einer davon verlassen wir uns in der Regel auf bedingte Anweisungen unter der Haube, stellen sie dem Benutzer jedoch als einfache Schnittstelle dar. Andere Methoden, die dieses Muster implementieren, umfassen animieren() und css (). Können Sie sich vorstellen, warum diese Fassadenmuster verwendet werden??


Verhaltensmuster

Jedes objektorientierte Softwaresystem hat eine Kommunikation zwischen Objekten. Wenn diese Kommunikation nicht organisiert wird, kann es zu Fehlern kommen, die schwer zu finden und zu beheben sind. Verhaltensmuster schreiben verschiedene Methoden vor, um die Kommunikation zwischen Objekten zu organisieren. In diesem Abschnitt werden die Observer- und Mediator-Muster betrachtet.

Beobachtermuster

Das Observer-Muster ist das erste der beiden Verhaltensmuster, die wir durchmachen werden. Folgendes sagt es:

Im Observer Pattern kann ein Subjekt eine Liste von Beobachtern enthalten, die an seinem Lebenszyklus interessiert sind. Immer wenn das Subjekt etwas Interessantes tut, sendet es eine Benachrichtigung an seine Beobachter. Wenn ein Beobachter das Motiv nicht mehr hören möchte, kann das Motiv es aus seiner Liste entfernen.

Klingt ziemlich einfach, oder? Wir benötigen drei Methoden, um dieses Muster zu beschreiben:

  • veröffentlichen (Daten): Wird vom Betreff aufgerufen, wenn eine Benachrichtigung erfolgen soll. Einige Daten können von dieser Methode übergeben werden.
  • abonnieren (beobachter): Wird vom Betreff aufgerufen, um einen Beobachter zur Liste der Beobachter hinzuzufügen.
  • abbestellen (Beobachter): Vom Subjekt aufgerufen, um einen Beobachter aus seiner Liste der Beobachter zu entfernen.

Nun, es stellt sich heraus, dass die meisten modernen JavaScript-Bibliotheken diese drei Methoden als Teil ihrer benutzerdefinierten Ereignisinfrastruktur unterstützen. Normalerweise gibt es eine auf() oder anfügen() Methode, a auslösen() oder Feuer() Methode und ein aus() oder ablösen() Methode. Betrachten Sie den folgenden Ausschnitt:

// Wir erstellen einfach eine Zuordnung zwischen den jQuery-Ereignismethoden
// und die vom Observer Pattern vorgeschriebenen, aber Sie müssen nicht. var o = $ (); $ .subscribe = o.on.bind (o); $ .unsubscribe = o.off.bind (o); $ .publish = o.trigger.bind (o); // Verwendung document.on ('tweetsReceived', function (tweets) // führt einige Aktionen aus und löst dann ein Ereignis aus $ .publish ('tweetsShow', tweets);); // Wir können diese Veranstaltung abonnieren und dann unsere eigene Veranstaltung auslösen. $ .subscribe ('tweetsShow', function () // Die Tweets irgendwie anzeigen ... // // Eine Aktion veröffentlichen, nachdem sie angezeigt wurde. $ .publish ('tweetsDisplayed);); $ .subscribe ('tweetsDisplayed, function () …);

Das Observer-Muster ist eines der einfacheren zu implementierenden Muster, aber es ist sehr mächtig. JavaScript eignet sich gut, um dieses Muster zu übernehmen, da es natürlich ereignisbasiert ist. Wenn Sie das nächste Mal Webanwendungen entwickeln, denken Sie darüber nach, Module zu entwickeln, die lose miteinander gekoppelt sind, und verwenden das Observer-Muster als Kommunikationsmittel. Das Beobachtermuster kann problematisch werden, wenn zu viele Personen und Beobachter beteiligt sind. Dies kann in großen Systemen der Fall sein, und das nächste Muster, das wir betrachten, versucht, dieses Problem zu lösen.

Mediator-Muster

Das letzte Muster, das wir betrachten werden, ist das Mediator-Muster. Es ähnelt dem Observer-Muster, weist jedoch einige bemerkenswerte Unterschiede auf.

Das Mediator-Muster fördert die Verwendung eines einzigen gemeinsamen Betreffs, der die Kommunikation mit mehreren Objekten übernimmt. Alle Objekte kommunizieren über den Vermittler miteinander.

Eine gute reale Analogie wäre ein Air Traffic Tower, der die Kommunikation zwischen dem Flughafen und den Flügen übernimmt. In der Welt der Softwareentwicklung wird das Mediator-Muster häufig verwendet, da das System zu kompliziert wird. Durch das Platzieren von Vermittlern kann die Kommunikation über ein einzelnes Objekt abgewickelt werden, anstatt mehrere Objekte miteinander kommunizieren zu lassen. In diesem Sinne kann ein Vermittlermuster verwendet werden, um ein System zu ersetzen, das das Beobachtermuster implementiert.

Es gibt eine vereinfachte Implementierung des Mediator-Musters von Addy Osmani in diesem Kern. Lassen Sie uns darüber sprechen, wie Sie es verwenden können. Stellen Sie sich vor, Sie haben eine Web-App, mit der Benutzer auf ein Album klicken und Musik daraus abspielen können. Sie könnten einen Vermittler wie folgt einrichten:

$ ('# album'). on ('click', Funktion (e) e.preventDefault (); var albumId = $ (this) .id (); mediator.publish ("playAlbum", albumId);) ; var playAlbum = function (id) … mediator.publish ("albumStartedPlaying", songList: […], currentSong: "Without You"); ; var logAlbumPlayed = function (id) // Das Album im Backend protokollieren; var updateUserInterface = function (album) // Aktualisieren Sie die Benutzeroberfläche, um das Gespielte wiederzugeben; // Vermittlerabonnements mediator.subscribe ("playAlbum", playAlbum); mediator.subscribe ("playAlbum", logAlbumPlayed); mediator.subscribe ("albumStartedPlaying", updateUserInterface);

Der Vorteil dieses Musters gegenüber dem Observer-Muster besteht darin, dass ein einzelnes Objekt für die Kommunikation verantwortlich ist, wohingegen im Observer-Muster mehrere Objekte zuhören und sich gegenseitig abonnieren könnten.

Im Observer-Muster gibt es kein einzelnes Objekt, das eine Einschränkung kapselt. Stattdessen müssen der Beobachter und das Subjekt zusammenarbeiten, um die Einschränkung aufrechtzuerhalten. Kommunikationsmuster werden durch die Art und Weise bestimmt, in der Betrachter und Subjekte miteinander verbunden sind: Ein einzelnes Subjekt hat normalerweise viele Beobachter, und manchmal ist der Beobachter eines Subjekts ein Subjekt eines anderen Beobachters.


Fazit

Jemand hat es bereits in der Vergangenheit erfolgreich angewendet.

Das Tolle an Design Patterns ist, dass jemand es bereits in der Vergangenheit erfolgreich angewendet hat. Es gibt eine Menge Open-Source-Code, der verschiedene Muster in JavaScript implementiert. Als Entwickler müssen wir wissen, welche Muster verfügbar sind und wann sie angewendet werden. Ich hoffe, dieses Tutorial hat Ihnen geholfen, diese Fragen noch weiter zu beantworten.


Zusätzliche Lesung

Ein Großteil des Inhalts dieses Artikels ist in dem ausgezeichneten Buch Learning JavaScript Design Patterns von Addy Osmani zu finden. Es ist ein Online-Buch, das unter einer Creative Commons-Lizenz kostenlos veröffentlicht wurde. Das Buch behandelt ausführlich die Theorie und Implementierung vieler verschiedener Muster, sowohl in Vanilla JavaScript als auch in verschiedenen JS-Bibliotheken. Ich empfehle Ihnen, sich beim Start Ihres nächsten Projekts als Referenz zu informieren.