Heim > Web-Frontend > js-Tutorial > SOLID-Prinzipien für JavaScript

SOLID-Prinzipien für JavaScript

Linda Hamilton
Freigeben: 2024-12-28 19:34:11
Original
329 Leute haben es durchsucht

Die Einführung des OOP-Paradigmas hat wichtige Programmierkonzepte wie Vererbung, Polymorphismus, Abstraktion und Kapselung populär gemacht. OOP wurde schnell zu einem weithin akzeptierten Programmierparadigma mit Implementierung in mehreren Sprachen wie Java, C, C#, JavaScript und mehr. Das OOP-System wurde im Laufe der Zeit komplexer, seine Software blieb jedoch resistent gegen Änderungen. Um die Erweiterbarkeit der Software zu verbessern und die Codesteifigkeit zu verringern, führte Robert C. Martin (alias Uncle Bob) Anfang der 2000er Jahre die SOLID-Prinzipien ein.

SOLID ist ein Akronym, das aus einer Reihe von Prinzipien besteht – Single-Responsibility-Prinzip, Open-Closed-Prinzip, Liskov-Substitutionsprinzip, Interface-Segregation-Prinzip und Dependency-Inversion-Prinzip –, die Softwareentwicklern helfen, wartbar, skalierbar und flexibel zu entwerfen und zu schreiben Code. Sein Ziel? Verbesserung der Qualität von Software, die nach dem Paradigma der objektorientierten Programmierung (OOP) entwickelt wurde.

In diesem Artikel werden wir uns mit allen Prinzipien von SOLID befassen und veranschaulichen, wie sie mithilfe einer der beliebtesten Web-Programmiersprachen, JavaScript, implementiert werden.

Prinzip der Einzelverantwortung (SRP)

Der erste Buchstabe in SOLID steht für das Prinzip der Einzelverantwortung. Dieses Prinzip legt nahe, dass eine Klasse oder ein Modul nur eine Rolle erfüllen sollte.

Einfach ausgedrückt sollte eine Klasse eine einzige Verantwortung oder einen einzigen Grund für eine Änderung haben. Wenn eine Klasse mehr als eine Funktionalität verarbeitet, wird es schwierig, eine Funktionalität zu aktualisieren, ohne die anderen zu beeinträchtigen. Die daraus resultierenden Komplikationen könnten zu einem Fehler in der Softwareleistung führen. Um solche Probleme zu vermeiden, sollten wir unser Bestes tun, modulare Software zu schreiben, in der die Belange getrennt sind.

Wenn eine Klasse zu viele Verantwortlichkeiten oder Funktionen hat, kann es schwierig sein, sie zu ändern. Durch die Verwendung des Single-Responsibility-Prinzips können wir Code schreiben, der modular, einfacher zu warten und weniger fehleranfällig ist. Nehmen Sie zum Beispiel ein Personenmodell:

class Person {
    constructor(name, age, height, country){
      this.name = name
      this.age = age
      this.height = height
      this.country = country
  }
  getPersonCountry(){
    console.log(this.country)    
  }
  greetPerson(){
    console.log("Hi " + this.name)
  }
  static calculateAge(dob) { 
    const today = new Date(); 
    const birthDate = new Date(dob);

    let age = today.getFullYear() - birthDate.getFullYear(); 
    const monthDiff = today.getMonth() - birthDate.getMonth();

    if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) { 
      age--; 
    }
    return age; 
  } 
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Der obige Code scheint in Ordnung zu sein, oder? Nicht ganz. Der Beispielcode verstößt gegen das Prinzip der Einzelverantwortung. Anstatt das einzige Modell zu sein, aus dem andere Instanzen einer Person erstellt werden können, hat die Person-Klasse auch andere Verantwortlichkeiten wie berechneAge, grüßePerson und getPersonCountry.

Diese zusätzlichen Verantwortlichkeiten, die von der Person-Klasse übernommen werden, machen es schwierig, nur einen Aspekt des Codes zu ändern. Wenn Sie beispielsweise versucht haben, „calculeAge“ umzugestalten, müssen Sie möglicherweise auch das Person-Modell umgestalten. Je nachdem, wie kompakt und komplex unsere Codebasis ist, könnte es schwierig sein, den Code neu zu konfigurieren, ohne Fehler zu verursachen.

Lassen Sie uns versuchen, den Fehler zu revidieren. Wir können die Verantwortlichkeiten in verschiedene Klassen unterteilen, etwa so:

class Person {
    constructor(name, age, height, country){
      this.name = name
      this.age = age
      this.height = height
      this.country = country
  }
  getPersonCountry(){
    console.log(this.country)    
  }
  greetPerson(){
    console.log("Hi " + this.name)
  }
  static calculateAge(dob) { 
    const today = new Date(); 
    const birthDate = new Date(dob);

    let age = today.getFullYear() - birthDate.getFullYear(); 
    const monthDiff = today.getMonth() - birthDate.getMonth();

    if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) { 
      age--; 
    }
    return age; 
  } 
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Wie Sie dem Beispielcode oben entnehmen können, haben wir unsere Verantwortlichkeiten getrennt. Die Person-Klasse ist nun ein Modell, mit dem wir ein neues Personenobjekt erstellen können. Und die PersonUtils-Klasse hat nur eine Aufgabe: das Alter einer Person zu berechnen. Die PersonService-Klasse verarbeitet Begrüßungen und zeigt uns das Land jeder Person.

Wenn wir wollen, können wir diesen Prozess noch weiter verkürzen. Dem SRP folgend wollen wir die Verantwortung einer Klasse auf ein Minimum entkoppeln, damit bei Problemen Refactoring und Debugging ohne großen Aufwand durchgeführt werden können.

Durch die Aufteilung der Funktionalität in separate Klassen halten wir uns an das Prinzip der Einzelverantwortung und stellen sicher, dass jede Klasse für einen bestimmten Aspekt der Anwendung verantwortlich ist.

Bevor wir zum nächsten Prinzip übergehen, sollte beachtet werden, dass die Einhaltung des SRP nicht bedeutet, dass jede Klasse ausschließlich eine einzelne Methode oder Funktionalität enthalten sollte.

Die Einhaltung des Prinzips der Einzelverantwortung bedeutet jedoch, dass wir Klassen gezielt Funktionalitäten zuweisen sollten. Alles, was eine Klasse ausführt, sollte in jeder Hinsicht eng miteinander verbunden sein. Wir müssen darauf achten, dass nicht mehrere Klassen überall verstreut sind, und wir sollten auf jeden Fall aufgeblähte Klassen in unserer Codebasis vermeiden.

Open-Closed-Prinzip (OCP)

Das Open-Closed-Prinzip besagt, dass Softwarekomponenten (Klassen, Funktionen, Module usw.) offen für Erweiterungen und geschlossen für Änderungen sein sollten. Ich weiß, was Sie denken – ja, diese Vorstellung mag zunächst widersprüchlich erscheinen. Das OCP fordert jedoch lediglich, dass die Software so konzipiert ist, dass sie erweitert werden kann, ohne dass unbedingt der Quellcode geändert werden muss.

Das OCP ist für die Pflege großer Codebasen von entscheidender Bedeutung, da diese Richtlinie es Ihnen ermöglicht, neue Funktionen einzuführen, ohne dass das Risiko einer Beschädigung des Codes gering ist. Anstatt die bestehenden Klassen oder Module bei neuen Anforderungen zu modifizieren, sollten Sie die entsprechenden Klassen durch das Hinzufügen neuer Komponenten erweitern. Stellen Sie dabei sicher, dass die neue Komponente keine Fehler im System verursacht.

Das OC-Prinzip kann in JavaScript mithilfe der ES6-Klassenvererbungsfunktion erreicht werden.

Die folgenden Codeausschnitte veranschaulichen, wie das Open-Closed-Prinzip in JavaScript mithilfe des oben genannten ES6-Klassenschlüsselworts implementiert wird:

class Person {
    constructor(name, dateOfBirth, height, country){
      this.name = name
      this.dateOfBirth = dateOfBirth
      this.height = height
      this.country = country
  }
}

class PersonUtils {
  static calculateAge(dob) { 
    const today = new Date(); 
    const birthDate = new Date(dob);

    let age = today.getFullYear() - birthDate.getFullYear(); 
    const monthDiff = today.getMonth() - birthDate.getMonth();

    if(monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) { 
      age--; 
    }
    return age; 
  } 
}

const person = new Person("John", new Date(1994, 11, 23), "6ft", "USA"); 
console.log("Age: " + PersonUtils.calculateAge(person.dateOfBirth));

class PersonService {
  getPersonCountry(){
    console.log(this.country)    
  }
  greetPerson(){
    console.log("Hi " + this.name)
  }
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Der obige Code funktioniert gut, beschränkt sich jedoch auf die Berechnung nur der Fläche eines Rechtecks. Stellen Sie sich nun vor, dass es eine neue Anforderung zur Berechnung gibt. Nehmen wir zum Beispiel an, dass wir die Fläche eines Kreises berechnen müssen. Wir müssten die ShapeProcessor-Klasse ändern, um dem gerecht zu werden. Gemäß dem JavaScript ES6-Standard können wir diese Funktionalität jedoch erweitern, um Bereiche mit neuen Formen zu berücksichtigen, ohne unbedingt die ShapeProcessor-Klasse zu ändern.

Wir können das so machen:

class Person {
    constructor(name, age, height, country){
      this.name = name
      this.age = age
      this.height = height
      this.country = country
  }
  getPersonCountry(){
    console.log(this.country)    
  }
  greetPerson(){
    console.log("Hi " + this.name)
  }
  static calculateAge(dob) { 
    const today = new Date(); 
    const birthDate = new Date(dob);

    let age = today.getFullYear() - birthDate.getFullYear(); 
    const monthDiff = today.getMonth() - birthDate.getMonth();

    if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) { 
      age--; 
    }
    return age; 
  } 
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Im obigen Codeausschnitt haben wir die Funktionalität der Shape-Klasse durch die Verwendung des Schlüsselworts „extends“ erweitert. In jeder Unterklasse überschreiben wir die Implementierung der Methode „area()“. Nach diesem Prinzip können wir weitere Formen und Prozessbereiche hinzufügen, ohne die Funktionalität der ShapeProcessor-Klasse ändern zu müssen.

Warum ist das OCP wichtig?

  • Fehler reduzieren: OCP hilft, Fehler in einer großen Codebasis zu vermeiden, indem Systemänderungen vermieden werden.
  • Fördert die Softwareanpassungsfähigkeit: OCP verbessert auch die Leichtigkeit, mit der neue Funktionen zu Software hinzugefügt werden können, ohne den Quellcode zu beschädigen oder zu ändern.
  • Testen neuer Funktionen: OCP fördert Codeerweiterung statt Modifikation, wodurch es einfacher wird, neue Funktionen als Einheit zu testen, ohne die gesamte Codebasis zu beeinträchtigen.

Das Liskov-Substitutionsprinzip

Das Liskov-Substitutionsprinzip besagt, dass ein Objekt einer Unterklasse in der Lage sein sollte, ein Objekt einer Oberklasse zu ersetzen, ohne den Code zu beschädigen. Lassen Sie uns anhand eines Beispiels erklären, wie das funktioniert: Wenn L eine Unterklasse von P ist, dann sollte ein Objekt von L ein Objekt von P ersetzen, ohne das System zu beschädigen. Dies bedeutet lediglich, dass eine Unterklasse in der Lage sein sollte, eine Methode einer Oberklasse so zu überschreiben, dass das System nicht beschädigt wird.

In der Praxis sorgt das Liskov-Substitutionsprinzip dafür, dass folgende Bedingungen eingehalten werden:

  • Eine Unterklasse sollte Methoden der übergeordneten Klasse überschreiben, ohne den Code zu beschädigen
  • Eine Unterklasse sollte nicht vom Verhalten der übergeordneten Klasse abweichen, was bedeutet, dass Unterklassen nur Funktionalität hinzufügen, aber die Funktionalität der übergeordneten Klasse nicht ändern oder entfernen können
  • Der Code, der mit den Instanzen der übergeordneten Klasse arbeitet, sollte mit den Instanzen der Unterklassen funktionieren, ohne dass bekannt sein muss, dass sich die Klasse geändert hat

Es ist Zeit, das Liskov-Substitutionsprinzip anhand von JavaScript-Codebeispielen zu veranschaulichen. Schauen Sie mal rein:

class Person {
    constructor(name, dateOfBirth, height, country){
      this.name = name
      this.dateOfBirth = dateOfBirth
      this.height = height
      this.country = country
  }
}

class PersonUtils {
  static calculateAge(dob) { 
    const today = new Date(); 
    const birthDate = new Date(dob);

    let age = today.getFullYear() - birthDate.getFullYear(); 
    const monthDiff = today.getMonth() - birthDate.getMonth();

    if(monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) { 
      age--; 
    }
    return age; 
  } 
}

const person = new Person("John", new Date(1994, 11, 23), "6ft", "USA"); 
console.log("Age: " + PersonUtils.calculateAge(person.dateOfBirth));

class PersonService {
  getPersonCountry(){
    console.log(this.country)    
  }
  greetPerson(){
    console.log("Hi " + this.name)
  }
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Im obigen Codeausschnitt haben wir zwei Unterklassen (Fahrrad und Auto) und eine Oberklasse (Fahrzeug) erstellt. Für die Zwecke dieses Artikels haben wir eine einzelne Methode (OnEngine) für die Oberklasse implementiert.

Eine der Kernbedingungen für den LSP ist, dass Unterklassen die Funktionalität der übergeordneten Klassen überschreiben sollten, ohne den Code zu beschädigen. Lassen Sie uns vor diesem Hintergrund sehen, wie der Codeausschnitt, den wir gerade gesehen haben, gegen das Liskov-Substitutionsprinzip verstößt. In Wirklichkeit hat ein Auto einen Motor und kann einen Motor einschalten, aber ein Fahrrad hat technisch gesehen keinen Motor und kann daher keinen Motor einschalten. Daher kann ein Bicycle die OnEngine-Methode in der Vehicle-Klasse nicht überschreiben, ohne den Code zu beschädigen.

Wir haben jetzt den Abschnitt des Codes identifiziert, der gegen das Liskov-Substitutionsprinzip verstößt. Die Car-Klasse kann die OnEngine-Funktionalität in der Superklasse überschreiben und so implementieren, dass sie sich von anderen Fahrzeugen (z. B. einem Flugzeug) unterscheidet und der Code nicht beschädigt wird. Die Car-Klasse erfüllt das Liskov-Substitutionsprinzip.

Im folgenden Codeausschnitt veranschaulichen wir, wie der Code so strukturiert wird, dass er dem Liskov-Substitutionsprinzip entspricht:

class Person {
    constructor(name, age, height, country){
      this.name = name
      this.age = age
      this.height = height
      this.country = country
  }
  getPersonCountry(){
    console.log(this.country)    
  }
  greetPerson(){
    console.log("Hi " + this.name)
  }
  static calculateAge(dob) { 
    const today = new Date(); 
    const birthDate = new Date(dob);

    let age = today.getFullYear() - birthDate.getFullYear(); 
    const monthDiff = today.getMonth() - birthDate.getMonth();

    if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) { 
      age--; 
    }
    return age; 
  } 
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Hier ist ein einfaches Beispiel einer Fahrzeugklasse mit einer allgemeinen Funktionalität: Bewegung. Es ist eine allgemeine Überzeugung, dass sich alle Fahrzeuge bewegen; Sie bewegen sich einfach über unterschiedliche Mechanismen. Eine Möglichkeit, LSP zu veranschaulichen, besteht darin, die Methode move() zu überschreiben und sie so zu implementieren, dass sie darstellt, wie sich ein bestimmtes Fahrzeug, beispielsweise ein Auto, bewegen würde.

Dazu erstellen wir eine Car-Klasse, die die Vehicle-Klasse erweitert und die Move-Methode überschreibt, um sie an die Bewegung eines Autos anzupassen, etwa so:

class Person {
    constructor(name, dateOfBirth, height, country){
      this.name = name
      this.dateOfBirth = dateOfBirth
      this.height = height
      this.country = country
  }
}

class PersonUtils {
  static calculateAge(dob) { 
    const today = new Date(); 
    const birthDate = new Date(dob);

    let age = today.getFullYear() - birthDate.getFullYear(); 
    const monthDiff = today.getMonth() - birthDate.getMonth();

    if(monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) { 
      age--; 
    }
    return age; 
  } 
}

const person = new Person("John", new Date(1994, 11, 23), "6ft", "USA"); 
console.log("Age: " + PersonUtils.calculateAge(person.dateOfBirth));

class PersonService {
  getPersonCountry(){
    console.log(this.country)    
  }
  greetPerson(){
    console.log("Hi " + this.name)
  }
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Wir können die Bewegungsmethode immer noch in einer anderen Unterfahrzeugklasse implementieren – zum Beispiel – einem Flugzeug.

So würden wir das machen:

class Rectangle { 
  constructor(width, height) {
    this.width = width; 
    this.height = height; 
  } 
  area() { 
  return this.width * this.height; 
  } 
} 

class ShapeProcessor { 
    calculateArea(shape) { 
    if (shape instanceof Rectangle) { 
    return shape.area(); 
    } 
  }
}  
const rectangle = new Rectangle(10, 20); const shapeProcessor = new ShapeProcessor(); console.log(shapeProcessor.calculateArea(rectangle)); 
Nach dem Login kopieren
Nach dem Login kopieren

In diesen beiden Beispielen oben haben wir Schlüsselkonzepte wie Vererbung und Methodenüberschreibung veranschaulicht.

Hinweis: Eine Programmierfunktion, die es Unterklassen ermöglicht, eine bereits in der übergeordneten Klasse definierte Methode zu implementieren, wird als Methodenüberschreibung bezeichnet.

Lass uns die Hausarbeit erledigen und alles zusammenbauen, etwa so:

class Shape {
  area() {
    console.log("Override method area in subclass");
  }
}

class Rectangle extends Shape {
  constructor(width, height) {
    super();
    this.width = width;
    this.height = height;
  }

  area() {
    return this.width * this.height;
  }
}

class Circle extends Shape {
  constructor(radius) {
    super();
    this.radius = radius;
  }

  area() {
    return Math.PI * this.radius * this.radius;
  }
}

class ShapeProcessor {
  calculateArea(shape) {
    return shape.area();
  }
}

const rectangle = new Rectangle(20, 10);
const circle = new Circle(2);
const shapeProcessor = new ShapeProcessor();

console.log(shapeProcessor.calculateArea(rectangle));
console.log(shapeProcessor.calculateArea(circle));
Nach dem Login kopieren
Nach dem Login kopieren

Jetzt haben wir zwei Unterklassen, die eine einzelne Funktionalität von der übergeordneten Klasse erben und überschreiben und sie entsprechend ihren Anforderungen implementieren. Diese neue Implementierung beschädigt den Code nicht.

Prinzip der Schnittstellentrennung (ISP)

Das Prinzip der Schnittstellentrennung besagt, dass kein Client gezwungen werden sollte, von einer Schnittstelle abhängig zu sein, die er nicht verwendet. Wir wollen, dass wir kleinere, spezifischere Schnittstellen erstellen, die für die jeweiligen Kunden relevant sind, anstatt eine große, monolithische Schnittstelle zu haben, die Kunden dazu zwingt, Methoden zu implementieren, die sie nicht benötigen.

Die Kompaktheit unserer Schnittstellen erleichtert das Debuggen, Warten, Testen und Erweitern der Codebasis. Ohne den ISP könnte eine Änderung in einem Teil einer großen Schnittstelle Änderungen in unabhängigen Teilen der Codebasis erzwingen, was uns dazu veranlassen würde, Code-Refactoring durchzuführen, was in den meisten Fällen je nach Größe der Codebasis eine schwierige Aufgabe sein kann.

JavaScript verfügt im Gegensatz zu C-basierten Programmiersprachen wie Java nicht über eine integrierte Unterstützung für Schnittstellen. Es gibt jedoch Techniken, mit denen Schnittstellen in JavaScript implementiert werden.

Schnittstellen sind eine Reihe von Methodensignaturen, die eine Klasse implementieren muss.

In JavaScript definieren Sie eine Schnittstelle als Objekt mit Namen von Methoden- und Funktionssignaturen, etwa so:

class Person {
    constructor(name, age, height, country){
      this.name = name
      this.age = age
      this.height = height
      this.country = country
  }
  getPersonCountry(){
    console.log(this.country)    
  }
  greetPerson(){
    console.log("Hi " + this.name)
  }
  static calculateAge(dob) { 
    const today = new Date(); 
    const birthDate = new Date(dob);

    let age = today.getFullYear() - birthDate.getFullYear(); 
    const monthDiff = today.getMonth() - birthDate.getMonth();

    if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) { 
      age--; 
    }
    return age; 
  } 
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Um eine Schnittstelle in JavaScript zu implementieren, erstellen Sie eine Klasse und stellen Sie sicher, dass sie Methoden mit denselben Namen und Signaturen enthält, die in der Schnittstelle angegeben sind:

class Person {
    constructor(name, dateOfBirth, height, country){
      this.name = name
      this.dateOfBirth = dateOfBirth
      this.height = height
      this.country = country
  }
}

class PersonUtils {
  static calculateAge(dob) { 
    const today = new Date(); 
    const birthDate = new Date(dob);

    let age = today.getFullYear() - birthDate.getFullYear(); 
    const monthDiff = today.getMonth() - birthDate.getMonth();

    if(monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) { 
      age--; 
    }
    return age; 
  } 
}

const person = new Person("John", new Date(1994, 11, 23), "6ft", "USA"); 
console.log("Age: " + PersonUtils.calculateAge(person.dateOfBirth));

class PersonService {
  getPersonCountry(){
    console.log(this.country)    
  }
  greetPerson(){
    console.log("Hi " + this.name)
  }
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Jetzt haben wir herausgefunden, wie man Schnittstellen in JavaScript erstellt und verwendet. Als nächstes müssen wir veranschaulichen, wie man Schnittstellen in JavaScript trennt, damit wir sehen können, wie alles zusammenpasst und die Wartung des Codes einfacher wird.

Im folgenden Beispiel verwenden wir einen Drucker, um das Prinzip der Schnittstellentrennung zu veranschaulichen.

Angenommen, wir haben einen Drucker, einen Scanner und ein Fax, erstellen wir eine Schnittstelle, die die Funktionen dieser Objekte definiert:

class Rectangle { 
  constructor(width, height) {
    this.width = width; 
    this.height = height; 
  } 
  area() { 
  return this.width * this.height; 
  } 
} 

class ShapeProcessor { 
    calculateArea(shape) { 
    if (shape instanceof Rectangle) { 
    return shape.area(); 
    } 
  }
}  
const rectangle = new Rectangle(10, 20); const shapeProcessor = new ShapeProcessor(); console.log(shapeProcessor.calculateArea(rectangle)); 
Nach dem Login kopieren
Nach dem Login kopieren

Im obigen Code haben wir eine Liste getrennter oder getrennter Schnittstellen erstellt, um eine große Schnittstelle zu schaffen, die alle diese Funktionen definiert. Indem wir diese Funktionalitäten in kleinere Teile und spezifischere Schnittstellen aufteilen, ermöglichen wir verschiedenen Clients, genau die Methoden zu implementieren, die sie benötigen, und lassen alle anderen Teile außen vor.

Im nächsten Schritt erstellen wir Klassen, die diese Schnittstellen implementieren. Nach dem Prinzip der Schnittstellentrennung implementiert jede Klasse nur die Methoden, die sie benötigt.

Wenn wir einen Basisdrucker implementieren möchten, der nur Dokumente drucken kann, können wir einfach die print()-Methode über die Druckerschnittstelle implementieren, etwa so:

class Shape {
  area() {
    console.log("Override method area in subclass");
  }
}

class Rectangle extends Shape {
  constructor(width, height) {
    super();
    this.width = width;
    this.height = height;
  }

  area() {
    return this.width * this.height;
  }
}

class Circle extends Shape {
  constructor(radius) {
    super();
    this.radius = radius;
  }

  area() {
    return Math.PI * this.radius * this.radius;
  }
}

class ShapeProcessor {
  calculateArea(shape) {
    return shape.area();
  }
}

const rectangle = new Rectangle(20, 10);
const circle = new Circle(2);
const shapeProcessor = new ShapeProcessor();

console.log(shapeProcessor.calculateArea(rectangle));
console.log(shapeProcessor.calculateArea(circle));
Nach dem Login kopieren
Nach dem Login kopieren

Diese Klasse implementiert nur das PrinterInterface. Es implementiert keine Scan- oder Faxmethode. Durch die Befolgung des Prinzips der Schnittstellentrennung hat der Client – ​​in diesem Fall die Druckerklasse – seine Komplexität reduziert und die Leistung einer Software verbessert.

Abhängigkeitsinversionsprinzip (DIP)

Nun zu unserem letzten Prinzip: dem Abhängigkeitsinversionsprinzip. Dieses Prinzip besagt, dass Module höherer Ebene (Geschäftslogik) auf Abstraktion basieren sollten, anstatt sich direkt auf Module niedrigerer Ebene (Konkretion) zu verlassen. Es hilft uns, Codeabhängigkeiten zu reduzieren und bietet Entwicklern die Flexibilität, Anwendungen auf höheren Ebenen ohne Komplikationen zu ändern und zu erweitern.

Warum bevorzugt das Abhängigkeitsinversionsprinzip die Abstraktion gegenüber direkten Abhängigkeiten? Das liegt daran, dass die Einführung von Abstraktionen die potenziellen Auswirkungen von Änderungen verringert, die Testbarkeit verbessert (Abstraktionen anstelle konkreter Implementierungen nachahmen) und ein höheres Maß an Flexibilität in Ihrem Code erreicht. Diese Regel erleichtert die Erweiterung von Softwarekomponenten durch einen modularen Ansatz und hilft uns auch, Komponenten auf niedriger Ebene zu ändern, ohne die Logik auf hoher Ebene zu beeinträchtigen.

Die Einhaltung des DIP erleichtert die Wartung, Erweiterung und Skalierung des Codes und verhindert so Fehler, die aufgrund von Änderungen im Code auftreten könnten. Es empfiehlt Entwicklern, eine lose Kopplung anstelle einer engen Kopplung zwischen Klassen zu verwenden. Im Allgemeinen erhalten Teams durch die Annahme einer Denkweise, die Abstraktionen Vorrang vor direkten Abhängigkeiten einräumt, die Flexibilität, neue Funktionalitäten anzupassen und hinzuzufügen oder alte Komponenten zu ändern, ohne dass es zu Unterbrechungen kommt. In JavaScript können wir DIP mithilfe des Dependency-Injection-Ansatzes implementieren, etwa so:

class Person {
    constructor(name, age, height, country){
      this.name = name
      this.age = age
      this.height = height
      this.country = country
  }
  getPersonCountry(){
    console.log(this.country)    
  }
  greetPerson(){
    console.log("Hi " + this.name)
  }
  static calculateAge(dob) { 
    const today = new Date(); 
    const birthDate = new Date(dob);

    let age = today.getFullYear() - birthDate.getFullYear(); 
    const monthDiff = today.getMonth() - birthDate.getMonth();

    if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) { 
      age--; 
    }
    return age; 
  } 
}
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren
Nach dem Login kopieren

Im obigen Basisbeispiel ist die Application-Klasse das High-Level-Modul, das von der Datenbankabstraktion abhängt. Wir haben zwei Datenbankklassen erstellt: MySQLDatabase und MongoDBDatabase. Die Datenbanken sind Low-Level-Module und ihre Instanzen werden in die Anwendungslaufzeit eingefügt, ohne die Anwendung selbst zu ändern.

Abschluss

Das SOLID-Prinzip ist ein grundlegender Baustein für skalierbares, wartbares und robustes Softwaredesign. Diese Prinzipien helfen Entwicklern, sauberen, modularen und anpassungsfähigen Code zu schreiben.

Das SOLID-Prinzip fördert zusammenhängende Funktionalität, Erweiterbarkeit ohne Modifikation, Objektsubstitution, Schnittstellentrennung und Abstraktion über konkrete Abhängigkeiten. Stellen Sie sicher, dass Sie die SOLID-Prinzipien in Ihren Code integrieren, um Fehler zu verhindern und alle Vorteile zu nutzen.


LogRocket: JavaScript-Fehler einfacher beheben, indem Sie den Kontext verstehen

Das Debuggen von Code ist immer eine mühsame Aufgabe. Aber je besser Sie Ihre Fehler verstehen, desto einfacher ist es, sie zu beheben.

LogRocket ermöglicht es Ihnen, diese Fehler auf neue und einzigartige Weise zu verstehen. Unsere Frontend-Überwachungslösung verfolgt die Benutzerinteraktion mit Ihren JavaScript-Frontends, um Ihnen die Möglichkeit zu geben, genau zu sehen, was der Benutzer getan hat, was zu einem Fehler geführt hat.

SOLID principles for JavaScript

LogRocket zeichnet Konsolenprotokolle, Seitenladezeiten, Stack-Traces, langsame Netzwerkanforderungen/-antworten mit Header-Körpern, Browser-Metadaten und benutzerdefinierten Protokollen auf. Es wird nie einfacher sein, die Auswirkungen Ihres JavaScript-Codes zu verstehen!

Probieren Sie es kostenlos aus.

Das obige ist der detaillierte Inhalt vonSOLID-Prinzipien für JavaScript. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Quelle:dev.to
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Neueste Artikel des Autors
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage