Vererbung ist ein sehr leistungsfähiger Mechanismus für die Wiederverwendung von Code. Aber sie muss richtig eingesetzt werden. Ich würde sagen, dass Vererbung richtig eingesetzt wird, wenn die Unterklasse auch ein Untertyp der Oberklasse ist. Wie bereits erwähnt, ist das Liskov-Substitutionsprinzip hier der entscheidende Punkt.
Unterklasse ist nicht dasselbe wie Untertyp. Sie können Unterklassen erstellen, die keine Subtypen sind (und in diesem Fall sollten Sie die Komposition verwenden). Um zu verstehen, was ein Untertyp ist, sollten wir zunächst erklären, was ein Typ ist.
Wenn wir sagen, dass die Zahl 5 vom Typ Integer ist, geben wir damit an, dass 5 zu einer Menge möglicher Werte gehört (siehe als Beispiel die möglichen Werte für die primitiven Java-Typen). Wir sagen auch, dass es eine gültige Menge von Methoden gibt, die ich mit dem Wert durchführen kann, wie Addition und Subtraktion. Und schließlich geben wir an, dass es eine Reihe von Eigenschaften gibt, die immer erfüllt sind, z. B. wenn ich die Werte 3 und 5 addiere, erhalte ich 8 als Ergebnis.
Ein weiteres Beispiel sind die abstrakten Datentypen Set of Integers und List of Integers, deren Werte auf Integer beschränkt sind. Sie unterstützen beide eine Reihe von Methoden, wie add(newValue) und size(). Und beide haben unterschiedliche Eigenschaften (Klasseninvariante), Sets erlaubt keine Duplikate, während List Duplikate zulässt (natürlich gibt es noch andere Eigenschaften, die beide erfüllen).
Ein Subtyp ist ebenfalls ein Typ, der in Beziehung zu einem anderen Typ steht, dem sogenannten übergeordneten Typ (oder Supertyp). Der Untertyp muss die Merkmale (Werte, Methoden und Eigenschaften) des übergeordneten Typs erfüllen. Die Beziehung bedeutet, dass der Supertyp in jedem Kontext, in dem er erwartet wird, durch einen Subtyp ersetzt werden kann, ohne das Verhalten der Ausführung zu beeinflussen. Schauen wir uns nun etwas Code an, um zu verdeutlichen, was ich sage. Angenommen, ich schreibe eine Liste von Ganzzahlen (in einer Art Pseudosprache):
class List {
data = new Array();
Integer size() {
return data.length;
}
add(Integer anInteger) {
data[data.length] = anInteger;
}
}
Dann schreibe ich die Menge der ganzen Zahlen als Unterklasse der Liste der ganzen Zahlen:
class Set, inheriting from: List {
add(Integer anInteger) {
if (data.notContains(anInteger)) {
super.add(anInteger);
}
}
}
Unsere Klasse Set of Integers ist eine Unterklasse von List of Integers, ist aber kein Subtyp, da sie nicht alle Eigenschaften der Klasse List erfüllt. Die Werte und die Signatur der Methoden sind erfüllt, aber die Eigenschaften sind es nicht. Das Verhalten der Methode add(Integer) wurde eindeutig geändert, wobei die Eigenschaften des übergeordneten Typs nicht beibehalten wurden. Stellen Sie sich die Situation aus der Sicht des Clients Ihrer Klassen vor. Er könnte eine Menge von Ganzzahlen erhalten, wo eine Liste von Ganzzahlen erwartet wird. Der Kunde möchte vielleicht einen Wert hinzufügen und diesen Wert zur Liste hinzugefügt bekommen, auch wenn dieser Wert bereits in der Liste vorhanden ist. Aber wenn der Wert bereits vorhanden ist, wird er nicht hinzugefügt. Eine große Überraschung für sie!
Dies ist ein klassisches Beispiel für eine unsachgemäße Verwendung der Vererbung. Verwenden Sie in diesem Fall Komposition.
(ein Fragment aus: Vererbung richtig einsetzen ).
6 Stimmen
Siehe auch welches Klassendesign besser ist
6 Stimmen
In einem Satz Vererbung ist öffentlich, wenn Sie eine öffentliche Methode haben und Sie ändern es ändert die veröffentlichte api. wenn Sie Zusammensetzung haben und das Objekt komponiert hat sich geändert, müssen Sie nicht Ihre veröffentlichte api ändern.