Hallo Spaß-Coder.
Wer von euch hat schon einmal eine abgeleitete Klasse (also Kindklasse) verwendet, und diese verhielt sich anders als die Ober(-/Vater)klasse und damit anders, als ihr es erwartet habt? Oder ihr verarbeitet die Ausnahmen (Exception) einer Klasse alle sehr sorgfältig und nun kommt eine Kindklasse der bisherigen Implementierung daher und wirft plötzlich einen neuen Typ einer Ausnahme? Das war so nicht abgesprochen (oder erwartet)! Hier kommt das dritte Prinzip der SOLID-Prinzipien ins Spiel.
Liskovsches Substitutionsprinzip (LSP)
Die Definition des LSP von Barbara Liskov und Jeanette Wing wurde nach Wikipedia so formuliert:
„Sei q(x) eine beweisbare Eigenschaft von Objekten x des Typs T. Dann soll q(y) für Objekte y des Typs S wahr sein, wobei S ein Untertyp von T ist.“
Damit ist doch alles gesagt, oder?
Erläuterung des Prinzips
Die oben genannten Definition mal anders formuliert sagt etwas aus wie:
„Eine Kindklasse sollte sich so verhalten, dass es keine Probleme (oder unerwartetes Verhalten) gibt, wenn sie anstelle der Oberklasse verwendet wird.“
Diese Aussage wird in den folgenden Regeln konkretisiert.
1. In einer abgeleiteten Klasse dürfen Methodenparameter generischer sein als die Parameter in der gleichen Methode der Oberklasse. Demnach darf in der Kindklasse als Methodenparameter eine Oberklasse des bisherigen Parameters verwendet werden. Rückgabewerte von Methoden dürfen spezifischer sein als in der Oberklasse. Demnach darf in der abgeleiteten Klasse eine Kindklasse des bisherigen Rückgabewertes verwendet werden.
Aufgrund der Ableitungshierarchie ist dies plausibel. Angenommen, ich tausche die Verwendung einer Klasse durch eine abgeleitete Kindklasse aus. Würde diese in einer Methode nun eine Kindklasse des bisherigen Parameters erwarten, wird dies in den bisherigen Implementierungen nicht berücksichtigt.
Beispiel: Ein BMW ist ein Auto ist ein Fahrzeug. Wenn ich nun ein BMW anstelle eines Autos erwarte, schlägt der Aufruf fehlt, da ein Auto kein BMW ist – sondern nur andersherum.
2. In der abgeleiteten Klasse dürfen keine neuen Exception-Typen geworfen werden. Werden alle bisherigen Ausnahmetypen fein säuberlich abgefangen wird der Code zur Laufzeit kaputt gehen, wenn nun in einer Kindklasse ein neuer Typ einer Ausnahme geworfen wird, da dieser Typ nicht abgefangen wird. Zulässig ist hingegen, ein Ausnahmetyp von einer verwendeten Ausnahme abzuleiten, da diese auch immer vom Typ der Oberklasse ist und damit die Ausnahmebehandlung vollständig bleibt.
3. Vorbedingungen dürfen in der Kindklasse nicht verstärkt werden. So dürfen keine neuen Dinge erwartet werden, die eine bisherige Verwendung nicht erfüllt. Dies würde alle bisherigen Verwendungen der Oberklasse beim Austausch gegen eine Kindklasse kaputt machen. Nachbedingungen dürfen in der Kindklasse nicht geschwächt werden. Dies bedeutet, dass eine Kindklasse nicht weniger als ihre Oberklasse zurückgeben darf, da hier bei der Verwendung bestimmte Erwartungen an die Rückgabewerte geknüpft sind.
Zu diesem Prinzip gibt es im gesamten Netz nur ein Beispiel: Quadrat und Rechteck. Quadrat darf nicht von Rechteck ableiten. Warum? Beide haben doch vier Seiten. Ein Quadrat hat jedoch eine Einschränkung gegenüber einem Rechteck – es haben nämlich alle Seiten dieselbe Länge, was bei einem Quadrat bei beiden Seitenpaaren unabhängig voneinander der Fall ist. Wenn nun eine Verwendung von Quadrat erwartet, dass Höhe und Breite unabhängig von einander gesetzt werden können, bricht dies das Prinzip – ein Quadrat hat nur eine und nur genau eine Kantenlänge.
Unsere Erfahrung mit dem LSP
Mir ist gerade nicht bekannt, dass wir das Liskovsche Substitutionsprinzip bewusst eingesetzt haben. Für mich ist der zweite genannte Punkt bezüglich der Ausnahmen interessant. Hier ein Bewusstsein dafür zu entwickeln, dass ich bei abgeleiteten Klassen keine neuen Ausnahmetypen definiere und werfe, sondern – falls erforderlich – immer nur bestehende Ausnahmetypen ableite und damit konkretisiere.
Zusammenfassung
Bei allen SOLID-Prinzipien baut ein Prinzip immer auf den vorhergehenden Prinzipien auf. Das LSP mag möglicherweise auf Anhieb wenig Relevanz für eure tägliche Programmierpraxis haben, aber auch hier geht es um die Austauschbarkeit von Komponenten, die genau eine Verantwortlichkeit haben. Wenn ihr Erfahrungen mit diesem Prinzip gemacht habt, lasst uns teilhaben und schreibt einen Kommentar dazu.
Viel Spaß beim Anwenden.
Eure Spaß-Coder.
Dieser Artikel basiert neben unseren Erfahrungen auf verschiedenen Internetquellen, unter anderem diesem Video bei Youtube:
-
Applying S.O.L.I.D. Principles in Microsoft .NET/C# (https://youtu.be/Whhi1C2PpaA)