1984 Stimmen

Bevorzugen Sie Komposition statt Vererbung?

Warum Komposition der Vererbung vorziehen? Welche Kompromisse gibt es für jeden Ansatz? Wann sollten Sie Vererbung der Komposition vorziehen?

6 Stimmen

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.

104voto

Pavel Feldman Punkte 4451

Während ich in kurzen Worten dem Satz "Lieber Komposition als Erbe" zustimmen würde, klingt das für mich sehr oft wie "lieber Kartoffeln als Coca-Cola". Es gibt Orte für Vererbung und Orte für Komposition. Sie müssen den Unterschied verstehen, dann wird diese Frage verschwinden. Was es für mich wirklich bedeutet, ist "wenn Sie Vererbung verwenden wollen - denken Sie noch einmal nach, wahrscheinlich brauchen Sie Komposition".

Sie sollten Kartoffeln gegenüber Coca-Cola bevorzugen, wenn Sie essen wollen, und Coca-Cola gegenüber Kartoffeln, wenn Sie trinken wollen.

Das Erstellen einer Unterklasse sollte mehr bedeuten als nur eine bequeme Möglichkeit, Methoden der Oberklasse aufzurufen. Sie sollten die Vererbung verwenden, wenn die Unterklasse sowohl strukturell als auch funktionell eine Oberklasse ist, wenn sie als Oberklasse verwendet werden kann und Sie dies auch tun werden. Wenn das nicht der Fall ist, handelt es sich nicht um Vererbung, sondern um etwas anderes. Eine Komposition liegt vor, wenn Ihr Objekt aus einem anderen besteht oder eine Beziehung zu diesem hat.

Für mich sieht es so aus, dass, wenn jemand nicht weiß, ob er eine Erbschaft oder eine Zusammensetzung braucht, das eigentliche Problem darin besteht, dass er nicht weiß, ob er trinken oder essen will. Denken Sie mehr über Ihren Problembereich nach, um ihn besser zu verstehen.

6 Stimmen

Das richtige Werkzeug für die richtige Aufgabe. Ein Hammer kann vielleicht besser auf Dinge einschlagen als ein Schraubenschlüssel, aber das bedeutet nicht, dass man einen Schraubenschlüssel als "minderwertigen Hammer" ansehen sollte. Vererbung kann hilfreich sein, wenn die Dinge, die der Unterklasse hinzugefügt werden, notwendig sind, damit sich das Objekt wie ein Objekt der Oberklasse verhält. Betrachten wir zum Beispiel eine Basisklasse InternalCombustionEngine mit einer abgeleiteten Klasse GasolineEngine . Letztere fügt Dinge wie Zündkerzen hinzu, die der Basisklasse fehlen, aber die Verwendung des Dings als InternalCombustionEngine führt dazu, dass die Zündkerzen verbraucht werden.

75voto

lcn Punkte 2069

Ich habe hier keine zufriedenstellende Antwort gefunden, also habe ich eine neue geschrieben.

Um zu verstehen, warum " lieber Komposition über Vererbung", müssen wir zunächst die in dieser verkürzten Redewendung weggelassene Annahme zurückgewinnen.

Die Vererbung hat zwei Vorteile: Subtypisierung und Subklassifizierung

  1. Subtypisierung bedeutet die Einhaltung einer Typ-(Schnittstellen-)Signatur, d. h. eines Satzes von APIs, und man kann einen Teil der Signatur überschreiben, um Subtyping-Polymorphismus zu erreichen.

  2. Unterklassifizierung bedeutet implizite Wiederverwendung von Methodenimplementierungen.

Mit den beiden Vorteilen sind zwei unterschiedliche Zwecke für die Vererbung verbunden: die Orientierung an Subtypen und die Orientierung an der Wiederverwendung von Code.

Wenn die Wiederverwendung von Code das allein Zweck kann die Unterklassifizierung einem mehr geben, als man braucht, d.h. einige öffentliche Methoden der Elternklasse sind für die Kindklasse nicht sehr sinnvoll. In diesem Fall ist die Komposition nicht der Vererbung vorzuziehen, sondern gefordert . Daher rührt auch der Begriff "ist-a" gegenüber "hat-a".

Nur wenn die Subtypisierung bezweckt wird, d. h. wenn die neue Klasse später polymorph verwendet werden soll, stellt sich das Problem der Wahl zwischen Vererbung und Komposition. Dies ist die Annahme, die in dem hier diskutierten verkürzten Idiom weggelassen wird.

Ein Subtyp muss einer Typsignatur entsprechen, d. h. die Komposition muss immer eine gleich große Anzahl von APIs des Typs offenlegen. Jetzt kommen die Kompromisse ins Spiel:

  1. Die Vererbung ermöglicht eine unkomplizierte Wiederverwendung von Code, wenn er nicht überschrieben wird, während bei der Komposition jede API neu kodiert werden muss, selbst wenn es sich nur um eine einfache Delegationsaufgabe handelt.

  2. Die Vererbung bietet eine unkomplizierte offene Rekursion über die interne polymorphe Stelle this d.h. der Aufruf einer überschreibenden Methode (oder sogar Typ ) in einer anderen Mitgliedsfunktion, entweder öffentlich oder privat (obwohl entmutigt ). Offene Rekursion kann sein simuliert durch Zusammensetzung aber es erfordert zusätzlichen Aufwand und ist nicht immer praktikabel(?). Diese Antwort zu einer duplizierten Frage spricht etwas Ähnliches.

  3. Die Vererbung zeigt auf geschützt Mitglieder. Dies unterbricht die Kapselung der übergeordneten Klasse, und wenn sie von einer Unterklasse verwendet wird, wird eine weitere Abhängigkeit zwischen dem Kind und seiner übergeordneten Klasse eingeführt.

  4. Die Komposition hat den Vorteil der Inversion der Kontrolle, und ihre Abhängigkeit kann dynamisch injiziert werden, wie in Dekorationsmuster y Proxy-Muster .

  5. Die Zusammensetzung hat den Vorteil, dass Kombinatorisch Programmierung, d. h. eine Arbeitsweise wie die zusammengesetztes Muster .

  6. Die Zusammensetzung folgt unmittelbar Programmierung an einer Schnittstelle .

  7. Die Zusammensetzung hat den Vorteil der einfachen Mehrfachvererbung .

In Anbetracht der oben genannten Abwägungen ergibt sich Folgendes lieber Komposition über Vererbung. Für eng verwandte Klassen, d.h. wenn die implizite Wiederverwendung von Code wirklich von Vorteil ist oder die magische Kraft der offenen Rekursion gewünscht wird, ist die Vererbung die erste Wahl.

1 Stimmen

Ich denke, Sie meinen, dass die Unterklassifizierung eine Untertypisierung nach sich zieht, wie es bei den meisten OOP-Sprachen der Fall ist. Um es anders zu formulieren: "Subklassifizierung bedeutet implizite Wiederverwendung von Methodenimplementierungen UND die einer Typ-(Schnittstellen-)Signatur entsprechen.

65voto

Mike Valenty Punkte 8755

Vererbung ist ziemlich verlockend, vor allem wenn man aus dem prozeduralen Bereich kommt, und sieht oft trügerisch elegant aus. Ich meine, alles, was ich tun muss, ist, dieses eine Bit an Funktionalität zu einer anderen Klasse hinzuzufügen, richtig? Nun, eines der Probleme ist, dass

Vererbung ist wahrscheinlich die schlechteste Form der Kopplung, die man haben kann.

Ihre Basisklasse durchbricht die Kapselung, indem sie den Unterklassen Implementierungsdetails in Form von geschützten Mitgliedern preisgibt. Dadurch wird Ihr System starr und anfällig. Der tragischere Fehler ist jedoch, dass die neue Unterklasse das gesamte Gepäck und die Meinung der Vererbungskette mit sich bringt.

Der Artikel, Vererbung ist böse: Das epische Versagen des DataAnnotationsModelBinders geht ein Beispiel für diese Vorgehensweise in C# durch. Es zeigt die Verwendung von Vererbung, wenn Komposition hätte verwendet werden sollen, und wie es refaktorisiert werden könnte.

5 Stimmen

Vererbung ist weder gut noch schlecht, sie ist lediglich ein Spezialfall der Zusammensetzung. In der Tat implementiert die Unterklasse eine ähnliche Funktionalität wie die Oberklasse. Wenn die von Ihnen vorgeschlagene Unterklasse keine Neuimplementierung ist, sondern lediglich mit die Funktionalität der Oberklasse, dann haben Sie die Vererbung falsch eingesetzt. Das ist ein Fehler des Programmierers und kein Vorwurf an die Vererbung.

60voto

Boris Dalstein Punkte 5965

Wann können Sie die Komposition verwenden?

Sie können immer die Komposition verwenden. In einigen Fällen ist auch die Vererbung möglich und kann zu einer leistungsfähigeren und/oder intuitiveren API führen, aber die Komposition ist immer eine Option.

Wann können Sie die Vererbung nutzen?

Es wird oft gesagt, dass, wenn "a bar is a foo", dann die Klasse Bar kann die Klasse Foo . Leider ist dieser Test allein nicht zuverlässig, verwenden Sie stattdessen den folgenden Test:

  1. ein bar ist ein foo, UND
  2. Bars können alles, was Foos können.

Der erste Test stellt sicher, dass alle Getter de Foo sinnvoll sein in Bar (= gemeinsame Eigenschaften), während der zweite Test sicherstellt, dass alle Einsteller de Foo sinnvoll sein in Bar (= gemeinsame Funktionalität).

Beispiel: Hund/Tier

Ein Hund ist ein Tier UND Hunde können alles tun, was Tiere tun können (z. B. atmen, sich bewegen usw.). Daher ist die Klasse Dog puede die Klasse erben Animal .

Gegenbeispiel: Kreis/Ellipse

Ein Kreis ist eine Ellipse, ABER Kreise können nicht alles, was Ellipsen können. Zum Beispiel können Kreise nicht gestreckt werden, während Ellipsen dies können. Deshalb ist die Klasse Circle no puede die Klasse erben Ellipse .

Dies wird als die Kreis-Ellipse-Problem was nicht wirklich ein Problem ist, sondern eher ein Hinweis darauf, dass "a bar is a foo" an sich kein zuverlässiger Test ist. Insbesondere zeigt dieses Beispiel, dass abgeleitete Klassen erweitern. die Funktionalität von Basisklassen, niemals einschränken es. Andernfalls könnte die Basisklasse nicht polymorph verwendet werden. Das Hinzufügen des Tests "bars can do everything that foos can do" stellt sicher, dass die polymorphe Verwendung möglich ist, und ist äquivalent zu Liskov-Substitutionsprinzip :

Funktionen, die Zeiger oder Verweise auf Basisklassen verwenden, müssen in der Lage sein, Objekte abgeleiteter Klassen zu verwenden, ohne dies zu wissen

Wann sollten Sie die Vererbung nutzen?

Auch wenn Sie puede Vererbung zu verwenden, bedeutet nicht, dass Sie devrait : Die Verwendung von Komposition ist immer eine Option. Die Vererbung ist ein leistungsfähiges Werkzeug, das die implizite Wiederverwendung von Code und die dynamische Verteilung ermöglicht, aber sie bringt auch einige Nachteile mit sich, weshalb die Komposition oft bevorzugt wird. Die Kompromisse zwischen Vererbung und Komposition sind nicht offensichtlich und werden meiner Meinung nach am besten erklärt in lcn's Antwort .

Als Faustregel neige ich dazu, die Vererbung der Komposition vorzuziehen, wenn die polymorphe Verwendung voraussichtlich sehr häufig vorkommt. In diesem Fall kann die Leistung der dynamischen Abfertigung zu einer viel besser lesbaren und eleganten API führen. Zum Beispiel, eine polymorphe Klasse zu haben Widget in GUI-Frameworks, oder eine polymorphe Klasse Node in XML-Bibliotheken ermöglicht eine API, die viel lesbarer und intuitiver zu verwenden ist als eine Lösung, die nur auf Komposition basiert.

0 Stimmen

Die Szenarien "Kreis-Ellipse" und "Quadrat-Rechteck" sind schlechte Beispiele. Unterklassen sind immer komplexer als ihre Oberklasse, so dass das Problem konstruiert ist. Dieses Problem wird durch Umkehrung der Beziehung gelöst. Eine Ellipse leitet sich von einem Kreis und ein Rechteck von einem Quadrat ab. Es ist äußerst unsinnig, in diesen Szenarien Komposition zu verwenden.

0 Stimmen

@FuzzyLogic Stimmt, aber in meinem Beitrag wird die Verwendung der Komposition in diesem Fall nicht befürwortet. Ich habe nur gesagt, dass das Kreis-Ellipse-Problem ein großartiges Beispiel dafür ist, warum "is-a" allein kein guter Test ist, um zu dem Schluss zu kommen, dass Kreis von Ellipse abgeleitet werden sollte. Wenn wir zu dem Schluss kommen, dass Circle aufgrund der Verletzung von LSP nicht von Ellipse abgeleitet werden sollte, dann sind mögliche Optionen, die Beziehung umzukehren, oder Komposition zu verwenden, oder Template-Klassen zu verwenden, oder ein komplexeres Design mit zusätzlichen Klassen oder Hilfsfunktionen zu verwenden, usw... und die Entscheidung sollte natürlich von Fall zu Fall getroffen werden.

2 Stimmen

@FuzzyLogic Und wenn Sie neugierig sind, was ich für den speziellen Fall von Circle-Ellipse befürworten würde: Ich würde dafür plädieren, die Klasse Circle nicht zu implementieren. Das Problem bei der Umkehrung der Beziehung ist, dass es auch gegen LSP verstößt: Stellen Sie sich die Funktion computeArea(Circle* c) { return pi * square(c->radius()); } . Es ist offensichtlich kaputt, wenn eine Ellipse übergeben wird (was bedeutet radius() überhaupt?). Eine Ellipse ist kein Kreis und sollte als solche nicht von Circle abgeleitet werden.

50voto

dance2die Punkte 33736

In Java oder C# kann ein Objekt seinen Typ nicht mehr ändern, sobald es instanziiert wurde.

Wenn Ihr Objekt also als ein anderes Objekt erscheinen oder sich abhängig von einem Objektzustand oder Bedingungen anders verhalten soll, dann verwenden Sie Zusammensetzung : Siehe Staat y Strategie Entwurfsmuster.

Wenn das Objekt vom gleichen Typ sein muss, dann verwenden Sie Vererbung oder Schnittstellen implementieren.

10 Stimmen

+1 Ich habe festgestellt, dass die Vererbung in den meisten Situationen immer weniger funktioniert. Ich bevorzuge gemeinsame/vererbte Schnittstellen und die Komposition von Objekten....oder nennt man das Aggregation? Fragen Sie mich nicht, ich habe einen EE-Abschluss!!

0 Stimmen

Ich glaube, dass dies das häufigste Szenario ist, in dem "Komposition vor Vererbung" gilt, da theoretisch beides passen könnte. Zum Beispiel könnte man in einem Marketingsystem das Konzept eines Client . Dann wird ein neues Konzept einer PreferredClient taucht später auf. Sollte PreferredClient erben Client ? Ein bevorzugter Client ist doch ein Client, oder? Nun, nicht so schnell... wie Sie sagten, können Objekte ihre Klasse zur Laufzeit nicht ändern. Wie würden Sie das Modell client.makePreferred() Operation? Vielleicht liegt die Antwort in der Verwendung der Komposition mit einem fehlenden Konzept, einem Account vielleicht?

0 Stimmen

Anstatt verschiedene Arten von Client Klassen, vielleicht gibt es nur eine, die das Konzept einer Account die ein StandardAccount oder eine PreferredAccount ...

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X