Woher kommt dieses Konzept der „Bevorzugung der Zusammensetzung gegenüber der Vererbung“?


143

In den letzten Monaten scheint das Mantra "Komposition gegenüber Vererbung bevorzugen" aus dem Nichts aufgetaucht zu sein und fast zu einer Art Mem innerhalb der Programmgemeinschaft geworden zu sein. Und jedes Mal, wenn ich es sehe, bin ich ein bisschen verwirrt. Es ist, als ob jemand gesagt hätte "zugunsten von Bohrern gegenüber Hämmern". Nach meiner Erfahrung sind Komposition und Vererbung zwei verschiedene Werkzeuge mit unterschiedlichen Anwendungsfällen, und es macht keinen Sinn, sie so zu behandeln, als wären sie austauschbar und eines war dem anderen von Natur aus überlegen.

Außerdem sehe ich keine wirkliche Erklärung dafür, warum Vererbung schlecht und Zusammensetzung gut ist, was mich nur misstrauischer macht. Soll es nur im Glauben akzeptiert werden? Liskov-Substitution und Polymorphismus haben bekannte, eindeutige Vorteile, und IMO umfasst den gesamten Punkt der Verwendung objektorientierter Programmierung, und niemand erklärt jemals, warum sie zugunsten der Komposition verworfen werden sollten.

Weiß jemand, woher dieses Konzept kommt und welche Gründe dahinter stehen?


45
Wie andere darauf hingewiesen haben, ist es schon lange her - ich bin überrascht, dass Sie gerade davon hören. Es ist intuitiv für jeden, der lange Zeit große Systeme in Sprachen wie Java erstellt hat. Es ist der Kern eines jeden Interviews, das ich gebe, und wenn ein Kandidat anfängt, über Vererbung zu sprechen, bezweifle ich seine Fähigkeiten und seine Erfahrung. Hier ist eine gute Einführung, warum Vererbung eine spröde Lösung ist (es gibt viele andere): artima.com/lejava/articles/designprinciples4.html
Janx

6
@Janx: Vielleicht ist es das. Ich baue keine großen Systeme in Sprachen wie Java; Ich baue sie in Delphi, und ohne Liskov-Substitution und Polymorphismus würden wir nie etwas erreichen. Das Objektmodell unterscheidet sich in gewisser Weise von dem von Java oder C ++, und viele der Probleme, die mit dieser Maxime gelöst werden sollen, existieren in Delphi nicht wirklich oder sind viel weniger problematisch. Verschiedene Perspektiven aus verschiedenen Blickwinkeln, denke ich.
Mason Wheeler

14
Ich verbrachte einige Jahre mit dem Teambuilding relativ großer Systeme in Delphi, und hohe Erbbäume haben sicherlich unser Team gebissen und uns erhebliche Schmerzen bereitet. Ich vermute, dass Ihre Beachtung der SOLID-Prinzipien Ihnen hilft, die Problembereiche zu umgehen, nicht Ihre Verwendung von Delphi.
Bevan

8
Letzte Monate?!?
Jas

6
Meines Erachtens wurde dieses Konzept nie vollständig an die verschiedenen Sprachen angepasst, die sowohl die Schnittstellenvererbung (dh Subtypisierung mit reinen Schnittstellen) als auch die Implementierungsvererbung unterstützen. Zu viele Leute folgen diesem Mantra und verwenden nicht genügend Schnittstellen.
Uri

Antworten:


136

Obwohl ich glaube, dass ich schon lange vor GoF Diskussionen über Komposition und Vererbung gehört habe, kann ich nicht auf eine bestimmte Quelle eingehen. Könnte sowieso Booch gewesen sein.

<rant>

Ah, aber wie viele Mantras ist auch dieses nach typischen Gesichtspunkten degeneriert:

  1. Es wird mit einer detaillierten Erklärung und Argumentation von einer angesehenen Quelle eingeleitet, die den Slogan als Erinnerung an die ursprüngliche komplexe Diskussion prägt
  2. Es wird mit einem wissenden Teil des Clubs von ein paar Kennern für eine Weile geteilt, im Allgemeinen, wenn sie n00b-Fehler kommentieren
  3. Bald wird es sinnlos von Tausenden und Abertausenden wiederholt, die die Erklärung nie lesen, sie aber gerne als Ausrede benutzen, nicht zu denken , und als billige und einfache Möglichkeit, sich anderen überlegen zu fühlen
  4. Letztendlich kann keine vernünftige Entlarvung die "meme" Flut aufhalten - und das Paradigma degeneriert in Religion und Dogma.

Das Mem, das ursprünglich dazu gedacht war, n00bs zur Erleuchtung zu führen, wird jetzt als Keule verwendet, um sie bewusstlos zu machen.

Zusammensetzung und Vererbung sind sehr unterschiedliche Dinge und sollten nicht miteinander verwechselt werden. Es ist zwar richtig, dass Komposition verwendet werden kann, um Vererbung mit viel zusätzlicher Arbeit zu simulieren , dies macht Vererbung jedoch weder zu einem Bürger zweiter Klasse, noch macht es Komposition zum bevorzugten Sohn. Die Tatsache, dass viele n00bs versuchen, Vererbung als Abkürzung zu verwenden, macht den Mechanismus nicht ungültig, und fast alle n00bs lernen aus dem Fehler und verbessern sich dadurch.

Bitte denkt über eure Entwürfe nach und hört auf, Slogans auszusprechen.

</ rant>


16
Booch argumentiert, dass die Vererbung der Implementierung eine hohe Kopplung zwischen Klassen einführt. Andererseits betrachtet er Vererbung als das Schlüsselkonzept, das OOP von prozeduraler Programmierung unterscheidet.
Nemanja Trifunovic

13
Es sollte ein Wort für so etwas geben. Vielleicht vorzeitiges Aufstoßen ?
Jörg W Mittag

8
@ Jörg: Weißt du, was mit einem Begriff wie "Vorzeitiges Aufstoßen" passiert? Genau das, was oben erklärt wurde. :) (Übrigens, wann ist Aufstoßen jemals nicht verfrüht?)
Bjarke Freund-Hansen

6
@ Nemanja: Richtig. Die Frage ist, ob diese Kopplung wirklich schlecht ist. Wenn die Klassen konzeptionell stark gekoppelt sind und konzeptionell eine Supertyp-Subtyp-Beziehung bilden und niemals konzeptuell entkoppelt werden könnten, selbst wenn sie formal auf Sprachebene entkoppelt sind, ist eine starke Kopplung in Ordnung.
Dsimcha

5
Das Mantra ist einfach: "Nur weil du play-doh so gestalten kannst, dass es aussieht, heißt das nicht, dass du alles aus play-doh machen sollst." ;)
Evan Plaice

73

Erfahrung.

Wie Sie sagen, sind sie Werkzeuge für verschiedene Jobs, aber der Satz kam zustande, weil die Leute ihn nicht auf diese Weise benutzten.

Vererbung ist in erster Linie ein polymorphes Werkzeug, aber einige Leute versuchen, es zu verwenden, um Code wiederzuverwenden / weiterzugeben. Das Grundprinzip lautet: "Gut, wenn ich erbe, bekomme ich alle Methoden umsonst", ignoriert jedoch die Tatsache, dass diese beiden Klassen möglicherweise keine polymorphe Beziehung haben.

Warum also die Komposition der Vererbung vorziehen - na ja, weil die Beziehung zwischen Klassen meist nicht polymorph ist. Es existiert einfach, um die Leute daran zu erinnern, dass sie nicht mit einem Knie-Ruck reagieren, indem sie erben.


7
Sie können also grundsätzlich nicht sagen, dass Sie OOP überhaupt nicht verwenden sollten, wenn Sie die Liskov-Substitution nicht verstehen. Sie bevorzugen also die Komposition gegenüber der Vererbung, um den durch Inkompetenz verursachten Schaden zu begrenzen Codierer?
Mason Wheeler

13
@Mason: Wie jedes Mantra richtet sich "Komposition vor Vererbung bevorzugen" an Anfängerprogrammierer. Wenn Sie bereits wissen, wann Sie Vererbung und wann Komposition verwenden müssen, ist es sinnlos, ein solches Mantra zu wiederholen.
Dean Harding

7
@Dean - Ich bin mir nicht sicher, ob Anfänger mit denen identisch sind, die Best Practices für die Vererbung nicht verstehen. Ich denke, es steckt mehr dahinter. Schlechte Vererbung ist die Ursache für viele, viele Kopfschmerzen in meinem Job und es ist kein Code, der von Programmierern geschrieben wurde, die als "Anfänger" gelten würden.
Nicole

6
@Renesis: Das erste, woran ich denke, wenn ich das höre, ist: "Manche Leute haben zehn Jahre Erfahrung, und manche Leute haben ein Jahr Erfahrung, das zehnmal wiederholt wird."
Mason Wheeler

8
Ich habe gleich nach dem ersten "Getting" OO ein ziemlich großes Projekt entworfen und mich stark darauf verlassen. Obwohl es gut funktionierte, fand ich das Design ständig brüchig - was hier und da kleinere Irritationen verursachte und manchmal bestimmte Refaktoren zu einer kompletten Hündin machte. Es ist schwierig, die ganze Erfahrung zu erklären, aber der Satz "Favor Compisition over Inheritence" beschreibt es genau. Beachten Sie, dass es nicht "Vererbung vermeiden" sagt oder sogar impliziert, es gibt Ihnen nur einen kleinen Anstoß, wenn die Wahl nicht offensichtlich ist.
Bill K

43

Es ist keine neue Idee, ich glaube, sie wurde tatsächlich in das 1994 erschienene GoF Design Patterns Book aufgenommen.

Das Hauptproblem bei der Vererbung ist, dass es sich um eine White-Box handelt. Per Definition müssen Sie die Implementierungsdetails der Klasse kennen, von der Sie erben. Bei der Komposition hingegen interessiert Sie nur die öffentliche Schnittstelle der Klasse, die Sie komponieren.

Aus dem GoF-Buch:

Durch die Vererbung wird eine Unterklasse den Details der Implementierung der übergeordneten Klasse ausgesetzt. Oft wird gesagt, dass die Vererbung die Kapselung unterbricht.

Der Wikipedia-Artikel zum GoF-Buch hat eine anständige Einführung.


14
Dem stimme ich nicht zu. Sie müssen die Implementierungsdetails der Klasse, von der Sie erben, nicht kennen. nur die Öffentlichkeit und geschützte Mitglieder der Klasse ausgesetzt. Wenn Sie die Implementierungsdetails kennen müssen, dann tun entweder Sie oder derjenige, der die Basisklasse geschrieben hat, etwas Falsches, und wenn der Fehler in der Basisklasse liegt, hilft Ihnen die Komposition nicht, ihn zu beheben / zu umgehen.
Mason Wheeler

18
Wie können Sie mit etwas nicht einverstanden sein, das Sie nicht gelesen haben? Es gibt eine solide Seite und eine halbe Diskussion von GoF, von der Sie nur eine winzige Ansicht bekommen.
Philosodad

7
@pholosodad: Ich bin mit etwas nicht einverstanden, das ich nicht gelesen habe. Ich bin nicht einverstanden mit dem, was Dean geschrieben hat: "Per Definition müssen Sie die Implementierungsdetails der Klasse kennen, von der Sie erben", was ich gelesen habe.
Mason Wheeler

10
Was ich geschrieben habe, ist nur eine Zusammenfassung dessen, was im GoF-Buch beschrieben ist. Ich hätte es vielleicht ein wenig ausführlich formuliert (Sie müssen nicht alle Implementierungsdetails kennen), aber das ist der allgemeine Grund, warum die GoF sagt, dass Komposition der Vererbung vorgezogen wird.
Dean Harding

2
Korrigieren Sie mich, wenn ich falsch liege, aber ich sehe es als "Bevorzugen Sie explizite Klassenbeziehungen (dh Schnittstellen) gegenüber impliziten (dh Vererbung)." Ersteres sagt dir, was du brauchst, ohne dir zu sagen, wie es geht. Letzteres erklärt Ihnen nicht nur, wie es geht, sondern lässt Sie es später bereuen.
Evan Plaice


23

"Komposition über Vererbung" ist eine kurze (und anscheinend irreführende) Redewendung. "Wenn Sie der Meinung sind, dass die Daten (oder das Verhalten) einer Klasse in eine andere Klasse integriert werden sollten, ziehen Sie immer die Verwendung von Komposition in Betracht, bevor Sie blind Vererbung anwenden."

Warum ist das so? Da durch die Vererbung eine enge Kopplung zwischen den beiden Klassen während der Kompilierung entsteht. Im Gegensatz dazu ist die Zusammensetzung eine lose Kopplung, die unter anderem eine klare Trennung von Bedenken, die Möglichkeit des Wechsels von Abhängigkeiten zur Laufzeit und eine einfachere, isoliertere Abhängigkeitsprüfbarkeit ermöglicht.

Das bedeutet nur, dass die Vererbung mit Sorgfalt behandelt werden sollte, da sie mit Kosten verbunden ist und nicht, dass sie nicht nützlich ist. Tatsächlich ist "Komposition über Vererbung" häufig "Komposition + Vererbung über Vererbung", da Sie häufig möchten, dass Ihre komponierte Abhängigkeit eine abstrakte Oberklasse und nicht die konkrete Unterklasse selbst ist. Damit können Sie zur Laufzeit zwischen verschiedenen konkreten Implementierungen Ihrer Abhängigkeit wechseln.

Aus diesem Grund werden Sie (unter anderem) wahrscheinlich feststellen, dass Vererbung häufiger in Form von Schnittstellenimplementierung oder abstrakten Klassen verwendet wird als Vanilla-Vererbung.

Ein (metaphorisches) Beispiel könnte sein:

"Ich habe eine Snake-Klasse und möchte als Teil dieser Klasse angeben, was passiert, wenn die Snake beißt. Ich wäre versucht, die Snake eine BiterAnimal-Klasse mit der Bite () -Methode erben zu lassen und diese Methode zu überschreiben, um giftigen Biss widerzuspiegeln Aber Komposition über Vererbung warnt mich, dass ich stattdessen versuchen sollte, Komposition zu verwenden ... In meinem Fall könnte dies bedeuten, dass die Snake ein Bite-Mitglied hat. Die Bite-Klasse könnte abstrakt (oder eine Schnittstelle) mit mehreren Unterklassen sein Ich habe nette Dinge wie VenomousBite- und DryBite-Unterklassen und die Möglichkeit, den Biss in derselben Snake-Instanz zu ändern, in der die Schlange mit zunehmendem Alter wächst. Außerdem kann ich den Effekt eines Bisses in einer eigenen Klasse wieder in dieser Frost-Klasse verwenden , weil Frost beißt, aber kein BiterAnimal ist, und so weiter ... "


4
Sehr gutes Beispiel mit der Schlange. Ich habe kürzlich einen ähnlichen Fall mit meinen eigenen Kursen getroffen
Maksee,

22

Einige mögliche Argumente für die Zusammensetzung:

Komposition ist etwas sprach- / rahmenunabhängiger
Vererbung und was sie erzwingt / erfordert / aktiviert, unterscheidet sich zwischen den Sprachen in Bezug auf den Zugriff auf die Unter- / Oberklasse und die Auswirkungen auf die Leistung, die sie auf virtuelle Methoden usw. haben kann. Komposition ist recht einfach und erfordert Sehr geringe Sprachunterstützung und damit Implementierungen über verschiedene Plattformen / Frameworks hinweg können Kompositionsmuster einfacher gemeinsam nutzen.

Komposition ist eine sehr einfache und taktile Art, Objekte zu bauen
Vererbung ist relativ leicht zu verstehen, aber im wirklichen Leben noch nicht so leicht nachzuweisen. Viele Gegenstände im wirklichen Leben können in Teile zerlegt und zusammengesetzt werden. Angenommen, ein Fahrrad kann mit zwei Rädern, einem Rahmen, einem Sitz, einer Kette usw. gebaut werden. Während in einer Erbschaftsmetapher gesagt werden kann, dass ein Fahrrad ein Einrad ausdehnt, was zwar machbar ist, aber noch viel weiter vom tatsächlichen Bild entfernt ist als die Komposition (dies ist offensichtlich kein sehr gutes Erbschaftsbeispiel, aber der Punkt bleibt derselbe). Sogar die Wortvererbung (zumindest von den meisten US-Englischsprechern, die ich erwarten würde) ruft automatisch eine Bedeutung in der Art "Etwas, das von einem verstorbenen Verwandten überliefert wurde" auf, die in gewisser Weise mit seiner Bedeutung in der Software korreliert, aber immer noch nur lose passt.

Komposition ist fast immer flexibler
Mit Komposition können Sie jederzeit Ihr eigenes Verhalten definieren oder einfach diesen Teil Ihrer komponierten Teile freigeben. Auf diese Weise unterliegen Sie keiner der Einschränkungen, die durch eine Vererbungshierarchie (virtuell oder nicht virtuell usw.) auferlegt werden können.

Dies könnte daran liegen, dass die Komposition natürlich eine einfachere Metapher ist, die weniger theoretische Einschränkungen als die Vererbung aufweist. Darüber hinaus können diese besonderen Gründe während der Entwurfszeit offensichtlicher sein oder möglicherweise im Umgang mit einigen der Schmerzpunkte der Vererbung auffallen.

Disclaimer:
Offensichtlich ist es nicht diese klare Einbahnstraße. Jedes Design verdient die Bewertung mehrerer Muster / Werkzeuge. Vererbung ist weit verbreitet, hat viele Vorteile und ist oft eleganter als Komposition. Dies sind nur einige mögliche Gründe, die für die Komposition sprechen könnten.


1
"Offensichtlich ist es nicht diese klare Einbahnstraße." Wann ist Komposition nicht flexibler als Vererbung? Ich würde behaupten , dass es sehr viel ist eine Einbahnstraße. Vererbung ist nur syntaktischer Zucker für einen speziellen Kompositionsfall.
weberc2

Die Komposition ist häufig nicht flexibel, wenn eine Sprache verwendet wird, die die Komposition über explizit implementierte Schnittstellen implementiert , da sich diese Schnittstellen im Laufe der Zeit nicht abwärtskompatibel entwickeln dürfen. #jmtcw
MikeSchinkel

11

Vielleicht haben Sie in den letzten Monaten gerade bemerkt, dass Leute dies sagten, aber es ist guten Programmierern schon viel länger bekannt. Ich sage es mit Sicherheit seit etwa einem Jahrzehnt, wo es angebracht ist.

Der Sinn des Konzepts ist, dass die Vererbung einen großen konzeptionellen Aufwand mit sich bringt. Wenn Sie die Vererbung verwenden, enthält jeder einzelne Methodenaufruf einen impliziten Versand. Wenn Sie über tiefe Vererbungsbäume oder mehrere oder (noch schlimmer) beides verfügen, kann es zu einer königlichen PITA werden, wenn Sie herausfinden, wohin die bestimmte Methode in einem bestimmten Aufruf gesendet wird. Dadurch wird die korrekte Begründung des Codes komplexer und das Debuggen schwieriger.

Lassen Sie mich ein einfaches Beispiel zur Veranschaulichung geben. Angenommen, tief in einem Vererbungsbaum hat jemand eine Methode benannt foo. Dann kommt jemand anderes und fügt foooben am Baum etwas hinzu , macht aber etwas anderes. (Dieser Fall tritt häufiger bei Mehrfachvererbung auf.) Diese Person, die in der Stammklasse arbeitet, hat die obskure untergeordnete Klasse beschädigt und erkennt sie wahrscheinlich nicht. Sie könnten eine 100-prozentige Abdeckung mit Komponententests haben und diesen Bruch nicht bemerken, da die Person an der Spitze nicht daran denkt, die untergeordnete Klasse zu testen, und die Tests für die untergeordnete Klasse nicht daran, die oben erstellten neuen Methoden zu testen . (Zugegeben, es gibt Möglichkeiten, Komponententests zu schreiben, die dies auffangen, aber es gibt auch Fälle, in denen es nicht einfach ist, Tests so zu schreiben.)

Wenn Sie dagegen Komposition verwenden, wird bei jedem Anruf in der Regel klarer, an wen Sie den Anruf weiterleiten. (OK, wenn Sie eine Invertierung der Steuerung verwenden, z. B. mit Abhängigkeitsinjektion, kann es auch problematisch sein, herauszufinden, wohin der Aufruf geht. In der Regel ist es jedoch einfacher, dies herauszufinden.) Dies erleichtert die Überlegung. Als Bonus führt die Komposition dazu, dass Methoden voneinander getrennt werden. Das obige Beispiel sollte dort nicht vorkommen, da die untergeordnete Klasse zu einer undurchsichtigen Komponente übergehen würde und es nie eine Frage gibt, ob der Aufruf von foofür die undurchsichtige Komponente oder das Hauptobjekt bestimmt war.

Jetzt haben Sie vollkommen recht, dass Vererbung und Zusammensetzung zwei sehr unterschiedliche Werkzeuge sind, die zwei verschiedenen Arten von Dingen dienen. Sicher, Vererbung ist mit konzeptionellem Aufwand verbunden, aber wenn es sich um das richtige Tool für den Job handelt, ist der konzeptionelle Aufwand geringer als der Versuch, es nicht zu verwenden und von Hand zu tun, was es für Sie bedeutet. Niemand, der weiß, was er tut, würde sagen, dass Sie niemals Vererbung verwenden sollten. Aber sei sicher, dass es das Richtige ist.

Leider lernen viele Entwickler etwas über objektorientierte Software, lernen etwas über Vererbung und setzen ihre neue Axt so oft wie möglich ein. Das bedeutet, dass sie versuchen, Vererbung zu verwenden, wenn Komposition das richtige Werkzeug war. Hoffentlich lernen sie mit der Zeit besser, aber häufig geschieht dies erst nach ein paar entfernten Gliedmaßen usw. Wenn man ihnen vorher sagt, dass es eine schlechte Idee ist, kann dies den Lernprozess beschleunigen und Verletzungen reduzieren.


3
Okay, ich denke, das ist aus C ++ - Sicht sinnvoll. Daran habe ich nie gedacht, weil es in Delphi kein Problem ist, was ich die meiste Zeit benutze. (Es gibt keine Mehrfachvererbung. Wenn Sie eine Methode in einer Basisklasse und eine andere Methode mit demselben Namen in einer abgeleiteten Klasse haben, die die Basismethode nicht überschreibt, gibt der Compiler eine Warnung aus, damit Sie nicht versehentlich enden mit dieser Art von Problem.)
Mason Wheeler

1
@Mason: Anders Version von Object Pascal (auch bekannt als Delphi) ist C ++ und Java überlegen, wenn es um das fragile Vererbungsproblem von Basisklassen geht. Im Gegensatz zu C ++ und Java ist das Überladen einer geerbten virtuellen Methode nicht implizit.
Bit-Twiddler

2
@ bit-twiddler, was Sie über C ++ und Java sagen, können Sie über Smalltalk, Perl, Python, Ruby, JavaScript, Common Lisp, Objective-C und jede andere Sprache sagen, die ich gelernt habe und die jede Form von OO-Unterstützung bietet. Das heißt, googeln deutet darauf hin, dass C # Object Pascal folgt.
btilly

3
Das liegt daran, dass Anders Hejlsberg Borlands Version von Object Pascal (alias Delphi) und C # entworfen hat.
Bit-Twiddler

8

Es ist eine Reaktion auf die Beobachtung, dass OO-Anfänger dazu neigen, Vererbung zu verwenden, wenn sie dies nicht müssen. Vererbung ist sicherlich keine schlechte Sache, kann aber überbeansprucht werden. Wenn eine Klasse nur Funktionalität von einer anderen benötigt, funktioniert die Komposition wahrscheinlich. Unter anderen Umständen funktioniert die Vererbung und die Komposition nicht.

Das Erben aus einer Klasse impliziert viele Dinge. Dies impliziert, dass ein Abgeleiteter eine Art von Basis ist (siehe das Liskov-Substitutionsprinzip für die wichtigsten Details). Wenn Sie eine Basis verwenden, ist es sinnvoll, einen Abgeleiteten zu verwenden. Es gibt dem Abgeleiteten Zugriff auf die geschützten Mitglieder und Mitgliederfunktionen von Base. Es handelt sich um eine enge Beziehung, was bedeutet, dass eine hohe Kopplung besteht und Änderungen an einer wahrscheinlich Änderungen an der anderen erfordern.

Kopplung ist eine schlechte Sache. Es erschwert das Verstehen und Ändern von Programmen. Wenn andere Dinge gleich sind, sollten Sie immer die Option mit weniger Kopplung auswählen.

Wenn also Komposition oder Vererbung die Aufgabe effektiv erledigen, wählen Sie Komposition, da es sich um eine geringere Kopplung handelt. Wenn die Komposition die Aufgabe nicht effektiv erledigt und die Vererbung die Aufgabe übernimmt, wählen Sie die Vererbung, da dies erforderlich ist.


"Unter anderen Umständen funktioniert die Vererbung und die Zusammensetzung nicht." Wann? Die Vererbung kann mithilfe des Kompositions- und Schnittstellenpolymorphismus modelliert werden. Daher wäre ich überrascht, wenn es Umstände gibt, für die die Komposition nicht funktioniert.
weberc2

8

Hier sind meine zwei Cent (abgesehen von all den bereits angesprochenen hervorragenden Punkten):

IMHO, es kommt auf die Tatsache an, dass die meisten Programmierer nicht wirklich Vererbung bekommen und es am Ende übertreiben, teilweise aufgrund dessen, wie dieses Konzept gelehrt wird. Dieses Konzept dient dazu, sie davon abzubringen, es zu übertreiben, anstatt sich darauf zu konzentrieren, ihnen beizubringen, wie man es richtig macht.

Jeder, der Zeit mit Unterrichten oder Mentoring verbracht hat, weiß, dass dies häufig der Fall ist, insbesondere bei neuen Entwicklern, die Erfahrung mit anderen Paradigmen haben:

Diese Entwickler glauben zunächst, dass Vererbung dieses beängstigende Konzept ist. Am Ende erstellen sie Typen mit Schnittstellenüberlappungen (z. B. dasselbe festgelegte Verhalten ohne gemeinsame Untertypen) und mit globalen Elementen zur Implementierung gemeinsamer Funktionen.

Dann lernen sie (oft als Folge von übereifrigem Lehren) die Vorteile der Vererbung kennen, aber es wird oft als die Allheilmittel für die Wiederverwendung gelehrt. Sie enden mit der Vorstellung, dass jedes gemeinsame Verhalten durch Vererbung geteilt werden muss. Dies liegt daran, dass der Schwerpunkt häufig auf der Vererbung der Implementierung und nicht auf der Untertypisierung liegt.

In 80% der Fälle ist das gut genug. Bei den anderen 20% beginnt das Problem. Um ein Umschreiben zu vermeiden und sicherzustellen, dass sie die Vorteile der gemeinsamen Implementierung nutzen, beginnen sie, ihre Hierarchie eher nach der beabsichtigten Implementierung als nach den zugrunde liegenden Abstraktionen zu gestalten. Der "Stapel erbt von doppelt verknüpfter Liste" ist ein gutes Beispiel dafür.

Die meisten Lehrer bestehen an dieser Stelle nicht darauf, das Konzept der Benutzeroberflächen einzuführen, da es sich entweder um ein anderes Konzept handelt oder Sie (in C ++) sie mit abstrakten Klassen und Mehrfachvererbung fälschen müssen, was in dieser Phase noch nicht gelehrt wird. In Java unterscheiden viele Lehrer "keine Mehrfachvererbung" oder "Mehrfachvererbung ist böse" nicht von der Bedeutung von Schnittstellen.

All dies wird durch die Tatsache verstärkt, dass wir so viel von der Schönheit gelernt haben, nicht überflüssigen Code mit Implementierungsvererbung schreiben zu müssen, dass Tonnen von einfachem Delegierungscode unnatürlich aussehen. Die Komposition sieht also chaotisch aus, weshalb wir diese Faustregeln brauchen, um neue Programmierer dazu zu bringen, sie trotzdem zu verwenden (was sie auch übertreiben).


Das ist ein wahrer Teil der Bildung. Sie müssen die höheren Kurse absolvieren, um zB den Rest von 20% zu erhalten, aber Sie als Student haben gerade die Grund- und und vielleicht auch die Zwischenkurse unterrichtet. Am Ende fühlst du dich gut unterrichtet, weil du deine Kurse gut gemacht hast (und siehst es einfach nicht als Stecknadel auf der Leiter). Es ist nur ein Teil der Realität und unsere beste Wette ist es, die Nebenwirkungen zu verstehen und nicht die Programmierer anzugreifen.
Unabhängige

8

In einem Kommentar erwähnte Mason, dass wir eines Tages über Vererbung sprechen würden, die als schädlich angesehen wird .

Hoffentlich.

Das Problem mit der Vererbung ist auf einmal einfach und tödlich, es respektiert nicht die Idee, dass ein Konzept eine Funktionalität haben sollte.

In den meisten OO-Sprachen haben Sie beim Erben von einer Basisklasse folgende Möglichkeiten:

  • von seiner Schnittstelle erben
  • von seiner Implementierung erben (sowohl Daten als auch Methoden)

Und hier wird der Ärger.

Dies ist nicht der einzige Ansatz, obwohl 00-Sprachen meistens daran hängen bleiben. Glücklicherweise gibt es in diesen Interfaces / abstrakte Klassen.

Auch die mangelnde Leichtigkeit, etwas anderes zu tun, trägt dazu bei, dass es größtenteils genutzt wird: Selbst wenn Sie dies wissen, würden Sie von einer Schnittstelle erben und die Basisklasse durch Komposition einbetten und die meisten Methodenaufrufe delegieren? Es wäre jedoch erheblich besser, Sie wären sogar gewarnt, wenn plötzlich eine neue Methode in der Benutzeroberfläche auftaucht und Sie bewusst auswählen müssten, wie sie implementiert werden soll.

Als Gegenpunkt, Haskellerlaubt nur das Liskov Prinzip zu verwenden , wenn „Ableiten“ von reinen Schnittstellen (genannt Konzepten) (1). Sie können nicht von einer vorhandenen Klasse abgeleitet werden, nur die Komposition ermöglicht das Einbetten der Daten.

(1) Konzepte können einen sinnvollen Standard für eine Implementierung darstellen. Da sie jedoch keine Daten enthalten, kann dieser Standard nur im Hinblick auf die anderen vom Konzept vorgeschlagenen Methoden oder im Hinblick auf Konstanten definiert werden.


7

Die einfache Antwort lautet: Vererbung hat eine größere Kopplung als Komposition. Wählen Sie aus zwei Optionen mit ansonsten äquivalenten Eigenschaften die weniger gekoppelte aus.


2
Aber das ist der springende Punkt. Sie sind nicht "ansonsten gleichwertig". Die Vererbung ermöglicht die Liskov-Substitution und den Polymorphismus, die den gesamten Zweck der Verwendung von OOP darstellen. Komposition nicht.
Mason Wheeler

4
Polymorphismus / LSP sind nicht "der ganze Punkt" von OOP, sie sind ein Merkmal. Es gibt auch Verkapselung, Abstraktionen usw. Vererbung impliziert eine "ist eine" Beziehung, Agregationsmodelle eine "hat eine" Beziehung.
Steve

@Steve: Sie können Abstraktionen und Kapselungen in jedem Paradigma durchführen, das die Erstellung von Datenstrukturen und Prozeduren / Funktionen unterstützt. Der Polymorphismus (in diesem Zusammenhang insbesondere das Dispatching virtueller Methoden) und die Liskov-Substitution sind jedoch nur in der objektorientierten Programmierung zu finden. Das habe ich damit gemeint.
Mason Wheeler

3
@Mason: Die Richtlinie lautet "Komposition gegenüber Vererbung bevorzugen". Dies bedeutet keinesfalls, dass die Vererbung nicht verwendet oder sogar vermieden wird. Alles in allem heißt es, wenn alle anderen Dinge gleich sind, wählen Sie die Komposition. Aber wenn Sie Vererbung benötigen, verwenden Sie es!
Jeffrey Faust

7

Ich denke, diese Art von Rat ist wie das Sprichwort "lieber fahren als fliegen". Das heißt, Flugzeuge haben alle möglichen Vorteile gegenüber Autos, aber das ist mit einer gewissen Komplexität verbunden. Also , wenn viele Leute versuchen , vom Stadtzentrum in die Vororte zu fliegen, die Ratschläge , die sie wirklich, wirklich hören müssen, ist , dass sie nicht fliegen müssen, und in der Tat, fliegen wird es nur auf lange Sicht komplizierter machen, auch wenn es kurzfristig cool / effizient / einfach erscheint. Während , wenn Sie sie fliegen müssen, ist es in der Regel sein , offensichtlich soll.

Ebenso kann die Vererbung Dinge bewirken, die die Komposition nicht kann, aber Sie sollten diese verwenden, wenn Sie sie benötigen und nicht, wenn Sie dies nicht tun. Wenn Sie also nie versucht sind, einfach anzunehmen, dass Sie Vererbung benötigen, wenn Sie dies nicht tun, brauchen Sie nicht den Rat "Komposition bevorzugen". Aber viele Menschen tun , und tun , dass die Beratung benötigen.

Es soll implizit sein, dass wenn Sie wirklich Vererbung brauchen, es offensichtlich ist, und Sie sollten es dann verwenden.

Auch Steven Lowes Antwort. Wirklich, wirklich das.


1
Ich mag Ihre Analogie
Barrylloyd

2

Vererbung ist nicht von Natur aus schlecht und Zusammensetzung ist nicht von Natur aus gut. Sie sind lediglich Werkzeuge, mit denen ein OO-Programmierer Software entwerfen kann.

Wenn Sie sich eine Klasse ansehen, tut sie mehr, als sie unbedingt tun sollte ( SRP )? Ist es unnötig, Funktionen zu duplizieren ( DRY ), oder ist es übermäßig an den Eigenschaften oder Methoden anderer Klassen interessiert ( Feature Envy )? Wenn die Klasse all diese Konzepte (und möglicherweise mehr) zu verletzen, ist es den Versuch , eine sein , Gottes Klasse . Dies sind eine Reihe von Problemen, die beim Entwerfen von Software auftreten können, von denen keines notwendigerweise ein Vererbungsproblem ist, die jedoch schnell zu starken Kopfschmerzen und spröden Abhängigkeiten führen können, wenn auch Polymorphismus angewendet wurde.

Die Ursache für das Problem dürfte weniger ein mangelndes Verständnis der Vererbung sein, sondern eher eine schlechte Wahl im Hinblick auf das Design oder das Nichterkennen von "Codegerüchen" in Bezug auf Klassen, die sich nicht an das Prinzip der einheitlichen Verantwortung halten . Polymorphismus und Liskov-Substitution müssen nicht zugunsten der Zusammensetzung verworfen werden. Der Polymorphismus selbst kann angewendet werden, ohne auf die Vererbung angewiesen zu sein. Dies sind alles recht komplementäre Konzepte. Wenn nachdenklich angewendet. Der Trick besteht darin, Ihren Code einfach und sauber zu halten und sich nicht übermäßig Gedanken über die Anzahl der Klassen zu machen, die Sie erstellen müssen, um ein robustes Systemdesign zu erstellen.

Was die Bevorzugung der Komposition gegenüber der Vererbung anbelangt, so ist dies nur ein weiterer Fall der durchdachten Anwendung der Gestaltungselemente, die im Hinblick auf das zu lösende Problem am sinnvollsten sind. Wenn Sie nicht brauchen Verhalten zu erben, dann sollten Sie sich vielleicht nicht als Zusammensetzung wird dazu beitragen , Unvereinbarkeiten zu vermeiden und erhebliche Anstrengungen Refactoring später. Wenn Sie andererseits feststellen, dass Sie viel Code wiederholen, sodass sich die gesamte Verdoppelung auf eine Gruppe ähnlicher Klassen konzentriert, kann das Erstellen eines gemeinsamen Vorfahren dazu beitragen, die Anzahl identischer Aufrufe und Klassen zu verringern Möglicherweise müssen Sie zwischen den einzelnen Klassen wiederholen. Sie bevorzugen also die Komposition, gehen aber nicht davon aus, dass die Vererbung niemals anwendbar ist.


-1

Das eigentliche Zitat stammt, glaube ich, aus Joshua Blochs "Effective Java", wo es der Titel eines der Kapitel ist. Er behauptet, dass die Vererbung innerhalb eines Pakets und überall dort sicher ist, wo eine Klasse speziell für die Erweiterung entworfen und dokumentiert wurde. Er behauptet, wie andere angemerkt haben, dass die Vererbung die Kapselung unterbricht, da eine Unterklasse von den Implementierungsdetails ihrer Oberklasse abhängt. Dies führe zu Zerbrechlichkeit.

Obwohl er es nicht erwähnt, scheint es mir, dass die einmalige Vererbung in Java bedeutet, dass die Komposition Ihnen viel mehr Flexibilität bietet als die Vererbung.

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.