Eine Schnittstelle definiert einen Vertrag oder eine Reihe von Methoden und Eigenschaften, die eine Klasse implementieren muss. Schnittstellen werden verwendet, um sicherzustellen, dass eine Klasse einem bestimmten Format folgt, sie stellen jedoch keine Implementierung von Methoden bereit, sondern nur deren Signaturen.
Immer wenn eine Klasse eine Schnittstelle implementiert, signiert sie alle Verträge (Methoden und Attribute) für die Schnittstelle. Jedes Attribut und jede Methode ist obligatorisch implementiert.
SOLID ist ein Akronym, das fünf grundlegende Prinzipien der objektorientierten Programmierung darstellt, vorgeschlagen von Robert C. Martin – Onkel Bob. Hier können Sie mehr über seinen Artikel lesen.
Diese Prinzipien zielen darauf ab, die Struktur und Wartung des Codes zu verbessern und ihn flexibler, skalierbarer und verständlicher zu machen. Solche Prinzipien helfen dem Programmierer, besser organisierte Codes zu erstellen, Verantwortlichkeiten aufzuteilen, Abhängigkeiten zu reduzieren, den Refactoring-Prozess zu vereinfachen und die Wiederverwendung von Code zu fördern.
Das „L“ im Akronym steht für „Liskov-Substitutionsprinzip“. Der Satz, mit dem Onkel Bob dieses Prinzip definierte, war:
„Abgeleitete Klassen müssen Basisklassen vollständig ersetzen können“
Es wird daher empfohlen, dass die abgeleitete Klasse so nah wie möglich an der Basisklasse sein sollte, damit die abgeleitete Klasse ihre Basisklasse ohne Änderungen im Code ersetzen kann.
Dieses Prinzip wurde 1988 von Barbara Liskov basierend auf der Theorie der Datenabstraktion und -typen eingeführt. Abgeleitet vom Konzept des Design by Contracts (DBC), das 1986 von Bertrand Meyer populär gemacht wurde.
Eine weitere Spezifikation dieses Prinzips ist:
Der Untertyp sollte ohne Überraschungen als Basistyp verwendet werden.
Bei der Programmierung können Änderungen und Überraschungen zu Problemen führen. Wenn eine Systemfunktion ersetzt werden muss, muss die neue Funktion dieselben Informationen bereitstellen, andernfalls kann das System ausfallen. Um sicherzustellen, dass Klasse S das gleiche Verhalten wie Basisklasse T aufweist, ist es wichtig, einen Vertrag (Schnittstelle oder abstrakte Klasse) zu verwenden, der die obligatorischen Methoden zur Implementierung der neuen Funktionalität definiert, um die Integrität der Ähnlichkeit zwischen Klasse S zu gewährleisten und Klasse T.
Stellen Sie sich eine Bird-Basisklasse mit einer fly()-Methode vor, die in zwei untergeordneten Klassen verwendet wird: Sparrow und Ostrich.
Datei:bird.java
class Bird { void fly() { System.out.println("I can fly!"); } } class Sparrow extends Bird { // Herda o comportamento de 'fly' da classe 'Bird' } class Ostrich extends Bird { @Override void fly() { throw new UnsupportedOperationException("I cannot fly"); } }
Datei:bird.ts
class Bird { fly() { console.log("I can fly!"); } } class Sparrow extends Bird {} class Ostrich extends Bird { fly() { throw new Error("I cannot fly"); } }
Hier hält sich die Sparrow-Klasse an LSP, da Spatzen tatsächlich fliegen können. Allerdings verstößt die Ostrich-Klasse gegen LSP, weil sie die voo()-Methode auf eine Weise überschreibt, die ihr Verhalten grundlegend ändert und die von der Ave-Klasse gesetzten Erwartungen bricht.
Wir müssen LSP anwenden, indem wir jede Spezifität der Sparrow- und Ostrich-Klassen in Verträge (Schnittstellen oder abstrakte Klassen, hier verwende ich Schnittstellen) unterteilen, die sie signieren müssen, um das Verhalten jedes einzelnen zu modulieren:
Datei:bird.java
interface Bird { String getName(); void makeSound(); } interface FlyingBird extends Bird { void fly(); } class Sparrow implements FlyingBird { private String name; public Sparrow(String name) { this.name = name; } @Override public String getName() { return this.name; } @Override public void makeSound() { System.out.println("Chirp chirp!"); } @Override public void fly() { System.out.println(this.name + " is flying!"); } } class Ostrich implements Bird { private String name; public Ostrich(String name) { this.name = name; } @Override public String getName() { return this.name; } @Override public void makeSound() { System.out.println("Boom boom!"); } } public class Main { public static void main(String[] args) { Sparrow sparrow = new Sparrow("Little Sparrow"); sparrow.makeSound(); // Chirp chirp! sparrow.fly(); // Little Sparrow is flying! Ostrich ostrich = new Ostrich("Ostrich"); ostrich.makeSound(); // Boom boom! ostrich.fly(); // Error: Method 'fly' does not exist on 'Ostrich' } }
Datei:bird.ts
interface Bird { name: string; makeSound(): void; } interface FlyingBird extends Bird { fly(): void; } class Sparrow implements FlyingBird { name: string; constructor(name: string) { this.name = name; } makeSound() { console.log("Chirp chirp!"); } fly() { console.log(`${this.name} is flying!`); } } class Ostrich implements Bird { name: string; constructor(name: string) { this.name = name; } makeSound() { console.log("Boom boom!"); } } const sparrow = new Sparrow("Little Sparrow"); sparrow.makeSound(); // Chirp chirp! sparrow.fly(); // Little Sparrow is flying! const ostrich = new Ostrich("Ostrich"); ostrich.makeSound(); // Boom boom! ostrich.fly(); // Error: Method 'fly' does not exist on 'Ostrich'
Korrekturerklärung
Vogelschnittstelle: Definiert Verhaltensweisen, die allen Vögeln gemeinsam sind, wie zum Beispiel makeSound(). Alle Vögel müssen diese Schnittstelle implementieren.
FlyingBird-Schnittstelle: Erbt von Ave und fügt das Verhalten „fly()“ hinzu, das speziell für Vögel gilt, die fliegen können.
Sparrow-Klasse: Implementiert die FlyingBird-Schnittstelle, da Spatzen fliegen können. Diese Klasse definiert das Verhalten sowohl beim Aussenden von Geräuschen als auch beim Fliegen.
Ostrich-Klasse: Implementiert nur die Bird-Schnittstelle, da Strauße nicht fliegen können. Diese Klasse verfügt nicht über die Methode fly() und verstößt daher nicht gegen LSP.
LSP ist entscheidend, um sicherzustellen, dass der Code modular, wiederverwendbar und leicht zu warten ist. Verstöße gegen das LSP können zu fragilem Code führen, der kaputt geht, wenn neue Unterklassen eingeführt oder bestehende Unterklassen geändert werden, da dies zu unerwartetem Verhalten in Teilen des Codes führen kann, die von der Oberklasse abhängen.
Die Subtyp-Substitution ermöglicht die Erweiterung eines Moduls ohne Modifikation, was für die Flexibilität des Open/Closed-Prinzips (OCP) unerlässlich ist, die durch das Liskov-Substitutionsprinzip ermöglicht wird. Verträge (implementiert über Schnittstellen oder abstrakte Klassen) sind für sicheres Design von entscheidender Bedeutung, müssen aber von Programmierern gut verstanden werden, um häufige Fehler in Legacy-Software zu vermeiden. Sie geben auch wertvolle Hinweise zur Implementierung und Nutzung des Codes, indem Sie einfach den jeweiligen Vertrag beachten.
Das Verstehen und Anwenden des Liskov-Substitutionsprinzips hilft Entwicklern, vorhersehbarere und stabilere objektorientierte Systeme zu erstellen.
Das obige ist der detaillierte Inhalt vonAnwendung des „Liskov-Substitutionsprinzips' mit Typescript und Java. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!