JavaScript-Entwurfsmuster

Der Autor des Materials, dessen Übersetzung wir veröffentlichen, sagt, dass beim Starten eines Projekts nicht sofort mit dem Schreiben von Code begonnen wird. Zunächst bestimmen sie den Zweck und die Grenzen des Projekts und identifizieren dann die Möglichkeiten, die es haben sollte. Danach schreiben sie entweder sofort den Code oder wählen, wenn es sich um ein recht komplexes Projekt handelt, geeignete Entwurfsmuster aus, die seine Grundlage bilden. In diesem Material geht es um JavaScript-Entwurfsmuster. Es ist in erster Linie für Anfänger-Entwickler konzipiert.



Was ist ein Designmuster?


Im Bereich der Softwareentwicklung ist ein Entwurfsmuster ein wiederholbarer Architekturentwurf, der eine Lösung für ein Entwurfsproblem in einem häufig auftretenden Kontext darstellt. Entwurfsmuster sind eine Zusammenfassung der Erfahrungen professioneller Softwareentwickler. Ein Entwurfsmuster kann als eine Art Muster betrachtet werden, nach dem Programme geschrieben werden.

Warum werden Designmuster benötigt?


Viele Programmierer halten Entwurfsmuster entweder für Zeitverschwendung oder wissen einfach nicht, wie sie richtig angewendet werden sollen. Die Verwendung eines geeigneten Musters kann jedoch dazu beitragen, besseren und verständlicheren Code zu schreiben, der aufgrund seiner Verständlichkeit leichter zu warten ist.

Das Wichtigste dabei ist vielleicht, dass die Verwendung von Mustern Softwareentwicklern so etwas wie ein Wörterbuch bekannter Begriffe bietet, die beispielsweise beim Parsen des Codes eines anderen sehr nützlich sind. Muster zeigen den Zweck bestimmter Fragmente des Programms für diejenigen, die versuchen, mit dem Gerät eines Projekts umzugehen.

Wenn Sie beispielsweise das Muster „Decorator“ verwenden, wird der neue Programmierer, der zum Projekt gekommen ist, sofort darüber informiert, welche Aufgaben ein bestimmter Code löst und warum er benötigt wird. Dank dessen kann ein solcher Programmierer mehr Zeit für die praktischen Aufgaben aufwenden, die das Programm löst, als zu versuchen, seine interne Struktur zu verstehen.

Nachdem wir herausgefunden haben, was Entwurfsmuster sind und wofür sie gedacht sind, werden wir uns den Mustern selbst zuwenden und ihre Implementierung mit JavaScript beschreiben.

Muster "Modul"


Ein Modul ist ein unabhängiger Code, der geändert werden kann, ohne dass dies Auswirkungen auf anderen Projektcode hat. Module ermöglichen es außerdem, ein Phänomen wie die Verschmutzung von Sichtbarkeitsbereichen zu vermeiden, da sie separate Sichtbarkeitsbereiche für die darin deklarierten Variablen erstellen. Für ein Projekt geschriebene Module können in anderen Projekten wiederverwendet werden, wenn ihre Mechanismen universell sind und nicht an die Besonderheiten eines bestimmten Projekts gebunden sind.

Module sind ein wesentlicher Bestandteil jeder modernen JavaScript-Anwendung. Sie helfen dabei, die Code-Sauberkeit aufrechtzuerhalten, Code in aussagekräftige Fragmente zu unterteilen und ihn zu organisieren. JavaScript bietet viele Möglichkeiten zum Erstellen von Modulen. Eine davon ist das Modul „Modul“.

Im Gegensatz zu anderen Programmiersprachen verfügt JavaScript nicht über Zugriffsmodifikatoren. Das heißt, Variablen können nicht als privat oder öffentlich deklariert werden. Infolgedessen wird das "Modul" -Muster auch verwendet, um das Konzept der Kapselung zu emulieren.

Dieses Muster verwendet IIFE (sofort aufgerufene funktionale Ausdrücke, Abschlüsse und Funktionsumfang, um dieses Konzept nachzuahmen. Zum Beispiel:

const myModule = (function() {   const privateVariable = 'Hello World';   function privateMethod() {    console.log(privateVariable);  }  return {    publicMethod: function() {      privateMethod();    }  } })(); myModule.publicMethod(); 

Da wir IIFE haben, wird der Code sofort ausgeführt und das vom Ausdruck zurückgegebene Objekt wird der Konstanten myModule . Aufgrund der Tatsache, dass ein Abschluss vorliegt, hat das zurückgegebene Objekt Zugriff auf Funktionen und Variablen, die in IIFE deklariert sind, auch nachdem IIFE seine Arbeit abgeschlossen hat.

Infolgedessen sind die in IIFE deklarierten Variablen und Funktionen vor den Mechanismen verborgen, die außerhalb ihrer Sichtbarkeit liegen. Sie erweisen sich als private Einheiten der myModule Konstante.

Nachdem dieser Code ausgeführt wurde, myModule aus:

 const myModule = { publicMethod: function() {   privateMethod(); }}; 

Das heißt, unter Bezugnahme auf diese Konstante können Sie die öffentliche Methode des publicMethod() , die wiederum die private Methode privateMethod() . Zum Beispiel:

 //  'Hello World' module.publicMethod(); 

Modulmuster öffnen


Das Revealing Module-Muster ist eine leicht verbesserte Version des von Christian Heilmann vorgeschlagenen Modulmusters. Das Problem mit dem "Modul" -Muster ist, dass wir öffentliche Funktionen erstellen müssen, um auf private Funktionen und Variablen zugreifen zu können.

In diesem Muster weisen wir den Eigenschaften des zurückgegebenen Objekts, das wir veröffentlichen möchten, private Funktionen zu. Aus diesem Grund wird dieses Muster als "Offenes Modul" bezeichnet. Betrachten Sie ein Beispiel:

 const myRevealingModule = (function() { let privateVar = 'Peter'; const publicVar  = 'Hello World'; function privateFunction() {   console.log('Name: '+ privateVar); } function publicSetName(name) {   privateVar = name; } function publicGetName() {   privateFunction(); } /**    ,     */ return {   setName: publicSetName,   greeting: publicVar,   getName: publicGetName }; })(); myRevealingModule.setName('Mark'); //  Name: Mark myRevealingModule.getName(); 

Die Verwendung dieses Musters vereinfacht das Verständnis, welche Funktionen und Variablen des Moduls öffentlich verfügbar sind, was zur Verbesserung der Lesbarkeit des Codes beiträgt.

Nach dem Ausführen von myRevealingModule sieht myRevealingModule myRevealingModule aus:

 const myRevealingModule = { setName: publicSetName, greeting: publicVar, getName: publicGetName }; 

Wir können beispielsweise die Methode myRevealingModule.setName('Mark') aufrufen, die auf die interne Funktion publicSetName . Die Methode myRevealingModule.getName() verweist auf die interne Funktion publicGetName . Zum Beispiel:

 myRevealingModule.setName('Mark'); //  Name: Mark myRevealingModule.getName(); 

Berücksichtigen Sie die Vorteile des Musters "Open Module" gegenüber dem Muster "Module":

  • Mit dem "offenen Modul" können Sie öffentliche versteckte Entitäten des Moduls erstellen (und diese gegebenenfalls wieder ausblenden), indem Sie für jede einzelne Entität nur eine Zeile im Objekt ändern, die nach IIFE zurückgegeben wird.
  • Das zurückgegebene Objekt enthält keine Funktionsdefinition. Alles rechts von seinen Eigenschaftsnamen ist in IIFE definiert. Dies hilft, den Code sauber und leicht lesbar zu halten.

Module in ES6


Vor der Veröffentlichung des ES6-Standards verfügte JavaScript nicht über ein Standardtool für die Arbeit mit Modulen. Daher mussten Entwickler Bibliotheken von Drittanbietern oder das Muster „Modul“ verwenden, um die entsprechenden Mechanismen zu implementieren. Mit dem Aufkommen von ES6 erschien jedoch ein Standardmodulsystem in JavaScript.

ES6-Module werden in Dateien gespeichert. Eine Datei kann nur ein Modul enthalten. Alles innerhalb des Moduls ist standardmäßig privat. Funktionen, Variablen und Klassen können mit dem Schlüsselwort export . Code innerhalb des Moduls wird immer im strengen Modus ausgeführt.

▍ Modul exportieren


Es gibt zwei Möglichkeiten, eine in einem Modul deklarierte Funktion oder Variable zu exportieren:

  • Der Export erfolgt durch Hinzufügen des Schlüsselworts export bevor eine Funktion oder Variable deklariert wird. Zum Beispiel:

     // utils.js export const greeting = 'Hello World'; export function sum(num1, num2) { console.log('Sum:', num1, num2); return num1 + num2; } export function subtract(num1, num2) { console.log('Subtract:', num1, num2); return num1 - num2; } //  -   function privateLog() { console.log('Private Function'); } 
  • Der Export erfolgt durch Hinzufügen des Schlüsselworts export am Ende des Codes, in dem die Namen der zu exportierenden Funktionen und Variablen aufgeführt sind. Zum Beispiel:

     // utils.js function multiply(num1, num2) { console.log('Multiply:', num1, num2); return num1 * num2; } function divide(num1, num2) { console.log('Divide:', num1, num2); return num1 / num2; } //    function privateLog() { console.log('Private Function'); } export {multiply, divide}; 

▍Importmodul


So wie es zwei Möglichkeiten zum Exportieren gibt, gibt es zwei Möglichkeiten zum Importieren von Modulen. Dies erfolgt mit dem Schlüsselwort import :

  • Importieren Sie mehrere ausgewählte Elemente. Zum Beispiel:

     // main.js //     import { sum, multiply } from './utils.js'; console.log(sum(3, 7)); console.log(multiply(3, 7)); 
  • Importieren Sie alles, was das Modul exportiert. Zum Beispiel:

     // main.js //  ,    import * as utils from './utils.js'; console.log(utils.sum(3, 7)); console.log(utils.multiply(3, 7)); 

▍ Aliase für exportierte und importierte Entitäten


Wenn die Namen von Funktionen oder Variablen, die in den Code exportiert werden, eine Kollision verursachen können, können sie während des Exports oder während des Imports geändert werden.

Um Objekte während des Exports umzubenennen, haben Sie folgende Möglichkeiten:

 // utils.js function sum(num1, num2) { console.log('Sum:', num1, num2); return num1 + num2; } function multiply(num1, num2) { console.log('Multiply:', num1, num2); return num1 * num2; } export {sum as add, multiply}; 

Um Objekte während des Imports umzubenennen, wird die folgende Konstruktion verwendet:

 // main.js import { add, multiply as mult } from './utils.js'; console.log(add(3, 7)); console.log(mult(3, 7)); 

Singleton-Muster


Das Muster "Singleton" oder "Singleton" ist ein Objekt, das nur in einer einzigen Kopie existieren kann. Im Rahmen der Anwendung dieses Musters wird eine neue Instanz einer Klasse erstellt, wenn sie noch nicht erstellt wurde. Wenn die Klasseninstanz bereits vorhanden ist, wird beim Versuch, auf den Konstruktor zuzugreifen, ein Verweis auf das entsprechende Objekt zurückgegeben. Nachfolgende Aufrufe des Konstruktors geben immer dasselbe Objekt zurück.

Tatsächlich gab es in JavaScript immer das, was wir als "Singleton" -Muster bezeichnen, aber sie nennen es nicht "Singleton", sondern "Objektliteral". Betrachten Sie ein Beispiel:

 const user = { name: 'Peter', age: 25, job: 'Teacher', greet: function() {   console.log('Hello!'); } }; 

Da jedes Objekt in JavaScript seinen eigenen Speicherbereich belegt und ihn nicht mit anderen Objekten teilt, erhalten wir bei jedem Zugriff auf die user einen Link zu demselben Objekt.

Das Singleton-Muster kann mithilfe der Konstruktorfunktion implementiert werden. Es sieht so aus:

 let instance = null; function User(name, age) { if(instance) {   return instance; } instance = this; this.name = name; this.age = age; return instance; } const user1 = new User('Peter', 25); const user2 = new User('Mark', 24); //  true console.log(user1 === user2); 

Wenn die Konstruktorfunktion aufgerufen wird, prüft sie zunächst, ob das instance vorhanden ist. Wenn die entsprechende Variable nicht initialisiert wird, wird this die instance . Wenn die Variable bereits einen Verweis auf ein Objekt hat, gibt der Konstruktor einfach eine instance , dh einen Verweis auf ein vorhandenes Objekt.

Das Singleton-Muster kann mithilfe des Modulmusters implementiert werden. Zum Beispiel:

 const singleton = (function() { let instance; function User(name, age) {   this.name = name;   this.age = age; } return {   getInstance: function(name, age) {     if(!instance) {       instance = new User(name, age);     }     return instance;   } } })(); const user1 = singleton.getInstance('Peter', 24); const user2 = singleton.getInstance('Mark', 26); // prints true console.log(user1 === user2); 

Hier erstellen wir eine neue user indem wir die Methode singleton.getInstance() aufrufen. Wenn eine Instanz des Objekts bereits vorhanden ist, gibt diese Methode diese einfach zurück. Wenn es noch kein solches Objekt gibt, erstellt die Methode eine neue Instanz davon, indem sie die User Konstruktorfunktion aufruft.

Fabrikmuster


Das Factory-Muster verwendet sogenannte Factory-Methoden, um Objekte zu erstellen. Sie müssen keine Klassen oder Konstruktorfunktionen angeben, die zum Erstellen von Objekten verwendet werden.

Dieses Muster wird verwendet, um Objekte in Fällen zu erstellen, in denen es nicht erforderlich ist, die Logik ihrer Erstellung öffentlich zu machen. Das Factory-Muster kann verwendet werden, wenn Sie abhängig von bestimmten Bedingungen unterschiedliche Objekte erstellen müssen. Zum Beispiel:

 class Car{ constructor(options) {   this.doors = options.doors || 4;   this.state = options.state || 'brand new';   this.color = options.color || 'white'; } } class Truck { constructor(options) {   this.doors = options.doors || 4;   this.state = options.state || 'used';   this.color = options.color || 'black'; } } class VehicleFactory { createVehicle(options) {   if(options.vehicleType === 'car') {     return new Car(options);   } else if(options.vehicleType === 'truck') {     return new Truck(options);     } } } 

Hier werden die Car und Truck Klassen angelegt, die die Verwendung bestimmter Standardwerte vorsehen. Sie werden verwendet, um car und truck Objekte zu erstellen. VehicleFactory wird auch die VehicleFactory Klasse deklariert, mit der neue Objekte basierend auf der Analyse der vehicleType Eigenschaft erstellt werden, die mit options an die entsprechende Methode des Objekts übergeben wird, das im Objekt zurückgegeben wird. So arbeiten Sie mit all dem:

 const factory = new VehicleFactory(); const car = factory.createVehicle({ vehicleType: 'car', doors: 4, color: 'silver', state: 'Brand New' }); const truck= factory.createVehicle({ vehicleType: 'truck', doors: 2, color: 'white', state: 'used' }); //  Car {doors: 4, state: "Brand New", color: "silver"} console.log(car); //  Truck {doors: 2, state: "used", color: "white"} console.log(truck); 

Hier wird das factory Objekt der VehicleFactory Klasse VehicleFactory . Danach können Sie Objekte der Klassen " Car oder " Truck erstellen, indem Sie die Methode factory.createVehicle() aufrufen und das vehicleType wobei die Eigenschaft " vehicleType auf " car oder " truck .

Dekorateur Muster


Das Decorator-Muster wird verwendet, um die Funktionalität von Objekten zu erweitern, ohne vorhandene Klassen oder Konstruktorfunktionen zu ändern. Dieses Muster kann verwendet werden, um Objekten bestimmte Funktionen hinzuzufügen, ohne den Code zu ändern, der für deren Erstellung verantwortlich ist.

Hier ist ein einfaches Beispiel für die Verwendung dieses Musters:

 function Car(name) { this.name = name; //    this.color = 'White'; } //   ,    const tesla= new Car('Tesla Model 3'); //   -    tesla.setColor = function(color) { this.color = color; } tesla.setPrice = function(price) { this.price = price; } tesla.setColor('black'); tesla.setPrice(49000); //  black console.log(tesla.color); 

Betrachten wir nun ein praktisches Beispiel für die Anwendung dieses Musters. Angenommen, die Kosten für Autos hängen von ihren Merkmalen und den zusätzlichen Funktionen ab, die ihnen zur Verfügung stehen. Ohne die Verwendung des Decorator-Musters müssten wir zur Beschreibung dieser Autos verschiedene Klassen für verschiedene Kombinationen dieser zusätzlichen Funktionen erstellen, von denen jede eine Methode zum Ermitteln der Kosten eines Autos hätte. Zum Beispiel könnte es so aussehen:

 class Car() { } class CarWithAC() { } class CarWithAutoTransmission { } class CarWithPowerLocks { } class CarWithACandPowerLocks { } 

Dank des fraglichen Musters können Sie ein Auto der Basisklasse erstellen, das beispielsweise ein Auto in der Grundkonfiguration beschreibt, dessen Kosten durch einen festen Betrag ausgedrückt werden. Danach kann das auf Basis dieser Klasse erstellte Standardobjekt mit Decorator-Funktionen erweitert werden. Das von dieser Funktion verarbeitete Standardauto erhält neue Möglichkeiten, die sich zusätzlich auf den Preis auswirken. Dieses Schema kann beispielsweise wie folgt implementiert werden:

 class Car { constructor() { //   this.cost = function() { return 20000; } } } // - function carWithAC(car) { car.hasAC = true; const prevCost = car.cost(); car.cost = function() {   return prevCost + 500; } } // - function carWithAutoTransmission(car) { car.hasAutoTransmission = true;  const prevCost = car.cost(); car.cost = function() {   return prevCost + 2000; } } // - function carWithPowerLocks(car) { car.hasPowerLocks = true; const prevCost = car.cost(); car.cost = function() {   return prevCost + 500; } } 

Hier erstellen wir zuerst die Basisklasse Car , mit der Objekte erstellt werden, die Autos als Standard darstellen. Anschließend erstellen wir mehrere Dekorationsfunktionen, mit denen wir die Objekte der Basisklasse Car um zusätzliche Eigenschaften erweitern können. Diese Funktionen nehmen die entsprechenden Objekte als Parameter. Danach fügen wir dem Objekt eine neue Eigenschaft hinzu, die angibt, mit welcher neuen Funktion das Auto ausgestattet wird, und definieren die cost des Objekts neu, die nun die neuen Kosten des Autos zurückgibt. Um das Auto mit Standardkonfiguration mit etwas Neuem auszustatten, können wir daher das folgende Design verwenden:

 const car = new Car(); console.log(car.cost()); carWithAC(car); carWithAutoTransmission(car); carWithPowerLocks(car); 

Danach können Sie die Kosten des Autos in einer verbesserten Konfiguration herausfinden:

 //       console.log(car.cost()); 

Zusammenfassung


In diesem Artikel haben wir verschiedene in JavaScript verwendete Entwurfsmuster untersucht. Tatsächlich gibt es jedoch noch viele Muster, mit denen eine Vielzahl von Problemen gelöst werden können.

Während die Kenntnis der verschiedenen Entwurfsmuster für den Programmierer wichtig ist, ist ihre angemessene Verwendung ebenso wichtig. Wenn der Programmierer die Muster und den Umfang ihrer Anwendung kennt und die vor ihm liegende Aufgabe analysiert, kann er verstehen, welche Art von Muster zur Lösung beitragen kann.

Liebe Leser! Welche Designmuster verwenden Sie am häufigsten?

Source: https://habr.com/ru/post/de427293/


All Articles