Das Argument "fürchterlich für das Gedächtnis" ist völlig falsch, aber es ist eine objektiv "schlechte Praxis". Wenn Sie von einer Klasse erben, erben Sie nicht nur die Felder und Methoden, die Sie interessieren. Stattdessen erben Sie alles . Jede deklarierte Methode, auch wenn sie für Sie nicht nützlich ist. Und vor allem erben Sie auch alle Verträge und Garantien, die die Klasse bietet.
Das Akronym SOLID bietet einige Heuristiken für eine gute objektorientierte Gestaltung. Hier haben das I nterface Segregation Principle (ISP) und das L iskov Substitution Pricinple (LSP) etwas zu sagen.
Der ISP fordert uns auf, unsere Schnittstellen so klein wie möglich zu halten. Aber durch das Erben von erhalten ArrayList
Sie viele, viele Methoden. Ist es sinnvoll get()
, remove()
, set()
(ersetzen) oder add()
(Einsatz) ein Kind - Knoten an einem bestimmten Index? Ist es sinnvoll, ensureCapacity()
von der zugrunde liegenden Liste? Was bedeutet es für sort()
einen Knoten? Sollen Nutzer Ihrer Klasse wirklich eine bekommen subList()
? Da Sie nicht gewünschte Methoden nicht ausblenden können, besteht die einzige Lösung darin, die ArrayList als Mitgliedsvariable zu haben und alle tatsächlich gewünschten Methoden weiterzuleiten:
private final ArrayList<Node> children = new ArrayList();
public void add(Node child) { children.add(child); }
public Iterator<Node> iterator() { return children.iterator(); }
Wenn Sie wirklich alle Methoden wollen, die Sie in der Dokumentation sehen, können wir zum LSP übergehen. Der LSP sagt uns, dass wir in der Lage sein müssen, die Unterklasse überall dort zu verwenden, wo die Elternklasse erwartet wird. Wenn eine Funktion einen ArrayList
as-Parameter annimmt und wir Node
stattdessen unseren übergeben , soll sich nichts ändern.
Die Kompatibilität von Unterklassen beginnt mit einfachen Dingen wie Typensignaturen. Wenn Sie eine Methode überschreiben, können Sie die Parametertypen nicht strenger festlegen, da dies möglicherweise Verwendungen ausschließt, die für die übergeordnete Klasse zulässig waren. Aber das ist etwas, was der Compiler für uns in Java überprüft.
Der LSP geht jedoch viel tiefer: Wir müssen die Kompatibilität mit allem aufrechterhalten, was die Dokumentation aller übergeordneten Klassen und Schnittstellen verspricht. In ihrer Antwort hat Lynn einen solchen Fall gefunden , wo die List
Schnittstelle (die Sie über geerbt haben ArrayList
) garantiert , wie die equals()
und hashCode()
Methoden funktionieren soll. Denn hashCode()
Ihnen wird sogar ein bestimmter Algorithmus vorgegeben, der exakt implementiert werden muss. Nehmen wir an, Sie haben folgendes geschrieben Node
:
public class Node extends ArrayList<Node> {
public final int value;
public Node(int value, Node... children) {
this.value = Value;
for (Node child : children)
add(child);
}
...
}
Dies setzt voraus, dass die value
nicht zum beitragen hashCode()
und nicht beeinflussen können equals()
. Die List
Schnittstelle, die Sie zu ehren versprechen, indem Sie sie erben new Node(0).equals(new Node(123))
, muss wahr sein.
Da durch das Erben von Klassen zu leicht versehentlich ein Versprechen einer übergeordneten Klasse gebrochen werden kann und in der Regel mehr Methoden verfügbar gemacht werden, als Sie beabsichtigt haben, wird im Allgemeinen vorgeschlagen, die Komposition der Vererbung vorzuziehen . Wenn Sie etwas erben müssen, wird empfohlen, nur Schnittstellen zu erben. Wenn Sie das Verhalten einer bestimmten Klasse wiederverwenden möchten, können Sie es als separates Objekt in einer Instanzvariablen speichern, sodass Ihnen nicht alle Versprechen und Anforderungen aufgezwungen werden.
Manchmal deutet unsere natürliche Sprache auf eine Vererbungsbeziehung hin: Ein Auto ist ein Fahrzeug. Ein Motorrad ist ein Fahrzeug. Soll ich Klassen definieren Car
und Motorcycle
die von einer Vehicle
Klasse erben ? Bei objektorientiertem Design geht es nicht darum, die reale Welt in unserem Code exakt wiederzugeben. Wir können die reichen Taxonomien der realen Welt nicht einfach in unserem Quellcode kodieren.
Ein solches Beispiel ist das Problem der Mitarbeiter-Chef-Modellierung. Wir haben mehrere Person
s, jedes mit einem Namen und einer Adresse. An Employee
ist ein Person
und hat ein Boss
. A Boss
ist auch a Person
. Soll ich also eine Person
Klasse erstellen , die von Boss
und geerbt wird Employee
? Jetzt habe ich ein Problem: Der Chef ist auch Angestellter und hat einen anderen Vorgesetzten. So scheint es, Boss
sollte sich verlängern Employee
. Aber das CompanyOwner
ist ein Boss
aber ist kein Employee
? Jede Art von Vererbungsgraph wird hier irgendwie zusammenbrechen.
Bei OOP geht es nicht um Hierarchien, Vererbung und Wiederverwendung vorhandener Klassen, sondern um die Verallgemeinerung des Verhaltens . In OOP geht es um „Ich habe eine Reihe von Objekten und möchte, dass sie etwas Besonderes tun - und es ist mir egal, wie.“ Dafür sind Schnittstellen gedacht. Wenn Sie die Iterable
Schnittstelle für Ihre implementieren, Node
weil Sie sie iterabel machen möchten, ist das vollkommen in Ordnung. Wenn Sie die Collection
Schnittstelle implementieren , weil Sie untergeordnete Knoten usw. hinzufügen / entfernen möchten, ist dies in Ordnung. Aber von einer anderen Klasse erben, weil es dir alles gibt, was nicht ist, oder zumindest nicht, wenn du es dir genau überlegt hast, wie oben beschrieben.