Warum sollte Java-Vererbung vermieden werden?


40

Sagte Jame Gosling

"Sie sollten die Vererbung der Implementierung nach Möglichkeit vermeiden."

Verwenden Sie stattdessen die Schnittstellenvererbung.

Aber wieso? Wie können wir vermeiden, die Struktur eines Objekts mit dem Schlüsselwort "extend" zu erben, und gleichzeitig unseren Code objektorientiert machen?

Könnte jemand bitte ein objektorientiertes Beispiel geben, das dieses Konzept in einem Szenario wie "Bestellen eines Buches in einem Buchladen" veranschaulicht?



Das Problem ist, dass Sie nur eine Klasse erweitern können.

Antworten:


38

Gosling hat nicht gesagt, dass er die Verwendung des Schlüsselworts "extend" vermeiden soll.

Vererbung ist an sich nicht schlecht und ein sehr leistungsfähiges (wesentliches) Werkzeug zum Erstellen von OO-Strukturen. Wenn es jedoch nicht richtig verwendet wird (d. H. Wenn es für etwas anderes als das Erstellen von Objektstrukturen verwendet wird), wird Code erstellt, der sehr eng gekoppelt und sehr schwer zu warten ist.

Da die Vererbung zum Erstellen polymorpher Strukturen verwendet werden sollte, kann sie genauso einfach mit Interfaces durchgeführt werden, daher die Anweisung, beim Entwerfen von Objektstrukturen Interfaces anstelle von Klassen zu verwenden. Wenn Sie Erweiterungen verwenden, um das Einfügen von Code von einem Objekt in ein anderes zu vermeiden, verwenden Sie diese möglicherweise falsch und sollten diese Funktionalität besser in einer speziellen Klasse behandeln und den Code stattdessen über die Komposition freigeben.

Lesen Sie mehr über das Liskov-Substitutionsprinzip und verstehen Sie es. Wenn Sie im Begriff sind, eine Klasse (oder eine Schnittstelle) zu erweitern, fragen Sie sich, ob Sie gegen das Prinzip verstoßen. Wenn ja, verwenden Sie die Vererbung höchstwahrscheinlich auf unnatürliche Weise. Es ist einfach zu machen und scheint oft das Richtige zu sein, aber es wird schwieriger, Ihren Code zu warten, zu testen und zu entwickeln.

--- Bearbeiten Diese Antwort ist ein ziemlich gutes Argument, um die Frage allgemeiner zu beantworten.


2
Die (Implementierungs-) Vererbung ist nicht unbedingt erforderlich, um OO-Strukturen zu erstellen. Sie können eine OO-Sprache ohne sie haben.
Andres F.

Könnten Sie ein Beispiel geben? Ich habe OO noch nie ohne Vererbung gesehen.
Newtopian

1
Das Problem ist, dass es keine akzeptierte Definition von OOP gibt. (Nebenbei bemerkt, bedauerte sogar Alan Kay die gemeinsame Auslegung seiner Definition, bei der derzeit "Objekte" anstelle von "Botschaften" im Vordergrund stehen). Hier ist ein Versuch, Ihre Frage zu beantworten . Ich stimme zu, dass es ungewöhnlich ist, OOP ohne Vererbung zu sehen. Daher war mein Argument nur, dass es nicht wesentlich ist ;)
Andres F.

9

In den Anfängen der objektorientierten Programmierung war die Vererbung der Implementierung der goldene Hammer der Wiederverwendung von Code. Wenn es in einer Klasse eine Funktionalität gab, die Sie brauchten, mussten Sie diese Klasse öffentlich erben (damals war C ++ häufiger als Java), und Sie erhielten diese Funktionalität "kostenlos".

Außer, dass es nicht wirklich kostenlos war. Das Ergebnis waren extrem verschlungene Vererbungshierarchien, die kaum zu verstehen waren, einschließlich rautenförmiger Vererbungsbäume, die schwer zu handhaben waren. Dies ist einer der Gründe, warum sich die Entwickler von Java dafür entschieden haben, die Mehrfachvererbung von Klassen nicht zu unterstützen.

Goslings Ratschläge (und andere Aussagen) waren starke Medizin für eine ziemlich schwere Krankheit, und es ist möglich, dass einige Babys mit dem Badewasser weggeworfen wurden. Es ist nichts Falsches daran, mit Vererbung eine Beziehung zwischen Klassen zu modellieren, die wirklich ist is-a. Wenn Sie jedoch nur eine Funktion aus einer anderen Klasse verwenden möchten, sollten Sie zuerst andere Optionen untersuchen.


6

Die Vererbung hat in letzter Zeit einen beängstigenden Ruf erlangt. Ein Grund, dies zu vermeiden, ist das Konstruktionsprinzip "Komposition statt Vererbung", das die Menschen grundsätzlich dazu ermutigt, Vererbung nicht zu verwenden, wenn dies nicht die wahre Beziehung ist, nur um Code zu sparen. Diese Art der Vererbung kann einige schwer zu findende und schwer zu behebende Fehler verursachen. Der Grund, warum Ihr Freund wahrscheinlich vorgeschlagen hat, stattdessen eine Schnittstelle zu verwenden, ist, dass Schnittstellen eine flexiblere und wartbarere Lösung für verteilte APIs bieten. Sie ermöglichen häufiger Änderungen an einer API, ohne den Code des Benutzers zu beschädigen, während das Offenlegen von Klassen häufig den Code des Benutzers beeinträchtigt. Das beste Beispiel dafür in .Net ist der Verwendungsunterschied zwischen List und IList.


5

Weil Sie nur eine Klasse erweitern können, während Sie viele Schnittstellen implementieren können.

Ich habe einmal gehört, dass Vererbung definiert, was Sie sind, aber Schnittstellen definieren eine Rolle, die Sie spielen können.

Schnittstellen ermöglichen auch eine bessere Verwendung des Polymorphismus.

Das könnte ein Teil des Grundes sein.


Warum die Gegenstimme?
Mahmoud Hossam

6
Die Antwort stellt den Karren vor das Pferd. Mit Java können Sie nur eine Klasse erweitern, da das Prinzip Gosling (Designer von Java) festgelegt ist. Es ist, als ob man sagt, Kinder sollten beim Fahrradfahren Helme tragen, weil das gesetzlich vorgeschrieben ist. Das stimmt, aber sowohl das Gesetz als auch der Ratschlag beruhen auf der Vermeidung von Kopfverletzungen.
JohnMcG

@JohnMcG Ich erkläre nicht die Design-Wahl, die Gosling getroffen hat, ich gebe nur einem Codierer Ratschläge, Codierer kümmern sich normalerweise nicht um das Sprachdesign.
Mahmoud Hossam

1

Das wurde mir auch beigebracht und ich bevorzuge Schnittstellen, wo es möglich ist (natürlich verwende ich immer noch Vererbung, wo es Sinn macht).

Eine Sache, die ich denke, ist es, Ihren Code von bestimmten Implementierungen zu entkoppeln. Angenommen, ich habe eine Klasse namens ConsoleWriter, die an eine Methode übergeben wird, um etwas auszuschreiben, und die es in die Konsole schreibt. Nehmen wir nun an, ich möchte zum Drucken in ein GUI-Fenster wechseln. Nun muss ich entweder die Methode modifizieren oder eine neue schreiben, die GUIWriter als Parameter verwendet. Wenn ich mit der Definition einer IWriter-Schnittstelle begonnen hätte und die Methode einen IWriter einbeziehen würde, könnte ich mit dem ConsoleWriter (der die IWriter-Schnittstelle implementieren würde) beginnen und später eine neue Klasse namens GUIWriter (der auch die IWriter-Schnittstelle implementiert) und dann I schreiben müsste nur die Klasse ausschalten, die bestanden wird.

Eine andere Sache (die für C # zutrifft, bei Java ist sie nicht sicher) ist, dass Sie nur eine Klasse erweitern, aber viele Schnittstellen implementieren können. Nehmen wir an, ich hatte Klassen mit den Namen Teacher, MathTeacher und HistoryTeacher. Jetzt erweitern sich MathTeacher und HistoryTeacher von Teacher, aber was ist, wenn wir eine Klasse wollen, die jemanden repräsentiert, der sowohl MathTeacher als auch HistoryTeacher ist. Es kann ziemlich chaotisch werden, wenn Sie versuchen, von mehreren Klassen zu erben, wenn Sie es immer nur einzeln tun können (es gibt Möglichkeiten, aber sie sind nicht genau optimal). Mit Interfaces können Sie zwei Interfaces namens IMathTeacher und IHistoryTeacher haben und dann eine Klasse, die von Teacher ausgeht, und diese beiden Interfaces implementieren.

Ein Nachteil bei der Verwendung von Schnittstellen ist, dass manchmal Leute doppelten Code sehen (da Sie die Implementierung für jede Klasse erstellen müssen, um die Schnittstelle zu implementieren), aber es gibt eine saubere Lösung für dieses Problem, zum Beispiel die Verwendung von Dingen wie Delegaten (nicht sicher) was das Java-Äquivalent dazu ist).

Der Hauptgrund für die Verwendung von Schnittstellen gegenüber Vererbungen ist die Entkopplung des Implementierungscodes. Sie sollten jedoch nicht glauben, dass Vererbung böse ist, da sie immer noch sehr nützlich ist.


2
"Sie können nur eine Klasse erweitern, aber viele Schnittstellen implementieren" Das gilt sowohl für Java als auch für C #.
FrustratedWithFormsDesigner

Eine mögliche Lösung für das Problem der Codeduplizierung besteht darin, eine abstrakte Klasse zu erstellen, die die Schnittstelle implementiert und diese erweitert. Verwenden Sie es jedoch mit Vorsicht. Es gibt nur wenige Fälle, in denen ich gesehen habe, dass es sinnvoll ist.
Michael K

0

Wenn Sie von einer Klasse erben, sind Sie nicht nur von dieser Klasse abhängig, sondern müssen auch alle impliziten Verträge einhalten, die von Kunden dieser Klasse erstellt wurden. Onkel Bob liefert ein großartiges Beispiel dafür, wie es einfach ist, das Liskov-Substitutionsprinzip auf subtile Weise zu verletzen, indem man das Dilemma der Rechteckerweiterung verwendet . Schnittstellen haben, da sie nicht implementiert sind, auch keine versteckten Verträge.


2
Ein Trottel: Das Circle-Ellipse-Problem tritt auch dann noch auf, wenn nur reine Interfaces verwendet werden, wenn die Objekte veränderbar sein sollen.
rwong
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.