"Komposition vor Vererbung" - Ist der einzige Grund, sich gegen Signaturänderungen zu verteidigen?


13

Diese Seite befürwortet Komposition über Vererbung mit dem folgenden Argument (umformuliert in meinen Worten):

Eine Änderung der Signatur einer Methode der Oberklasse (die in der Unterklasse nicht überschrieben wurde) führt an vielen Stellen zu zusätzlichen Änderungen, wenn Vererbung verwendet wird. Wenn wir jedoch Komposition verwenden, befindet sich die erforderliche zusätzliche Änderung nur an einer einzigen Stelle: der Unterklasse.

Ist das wirklich der einzige Grund, die Komposition der Vererbung vorzuziehen? Wenn dies der Fall ist, kann dieses Problem leicht behoben werden, indem ein Codierungsstil erzwungen wird, der das Überschreiben aller Methoden der Oberklasse befürwortet, auch wenn die Unterklasse die Implementierung nicht ändert (dh Dummy-Überschreibungen in die Unterklasse setzt). Vermisse ich hier etwas?




2
Das Zitat sagt nicht, dass es "der einzige" Grund ist.
Tulains Córdova

3
Die Komposition ist fast immer besser, da die Vererbung im Allgemeinen die Komplexität, die Kopplung und das Lesen von Code erhöht und die Flexibilität verringert. Aber es gibt bemerkenswerte Ausnahmen, manchmal schadet ein oder zwei Basisklassen nicht. Deshalb ist es lieber als immer .
Mark Rogers

Antworten:


27

Ich mag Analogien, also hier ist eine: Haben Sie jemals einen dieser Fernseher mit eingebautem Videorecorder gesehen? Wie wäre es mit einem Videorecorder und einem DVD-Player? Oder eine mit Blu-ray, DVD und Videorecorder. Oh und jetzt streamen alle, also müssen wir ein neues TV-Gerät entwerfen ...

Wenn Sie einen Fernseher besitzen, haben Sie höchstwahrscheinlich keinen wie den oben genannten. Wahrscheinlich haben die meisten davon noch nie existiert. Höchstwahrscheinlich verfügen Sie über einen Monitor oder ein Set mit zahlreichen Eingängen, sodass Sie nicht jedes Mal ein neues Set benötigen, wenn es einen neuen Typ von Eingabegerät gibt.

Vererbung ist wie beim Fernseher mit eingebautem Videorecorder. Wenn Sie drei verschiedene Verhaltenstypen haben, die Sie zusammen verwenden möchten, und jeder über zwei Implementierungsoptionen verfügt, benötigen Sie acht verschiedene Klassen, um all diese zu repräsentieren. Wenn Sie weitere Optionen hinzufügen, explodieren die Zahlen. Wenn Sie stattdessen Komposition verwenden, vermeiden Sie dieses kombinatorische Problem, und das Design ist in der Regel besser erweiterbar.


6
In den späten 90ern und frühen 2000ern gab es viele Fernseher mit Videorekordern und DVD-Playern.
JAB

@JAB und viele (die meisten?) Fernseher, die heute verkauft werden, unterstützen Streaming.
Casey

4
@JAB Und keiner von ihnen kann seinen DVD-Player verkaufen lassen, um einen Bluray-Player zu kaufen
Alexander - Reinstate Monica

1
@JAB Richtig. Die Aussage "Haben Sie jemals einen dieser Fernseher mit eingebautem Videorecorder gesehen?" impliziert, dass sie existieren.
JimmyJames

4
@Casey Denn offensichtlich wird es nie eine andere Technologie geben, die diese Technologie ablöst, oder? Ich wette, dass jeder dieser Fernseher auch Eingänge von externen Geräten unterstützt, für den Fall, dass jemand etwas anderes verwenden möchte, ohne einen neuen Fernseher zu kaufen. Oder vielmehr, nur wenige gebildete Käufer sind bereit, einen Fernseher zu kaufen, dem solche Eingänge fehlen. Ich gehe nicht davon aus, dass diese Ansätze nicht existieren, und ich würde auch nicht behaupten, dass Vererbung nicht existiert. Der Punkt ist, dass solche Ansätze von Natur aus weniger flexibel sind als die Komposition.
JimmyJames

4

Nein ist es nicht. Nach meiner Erfahrung ist Komposition über Vererbung das Ergebnis des Denkens an Ihre Software als eine Ansammlung von Objekten mit Verhaltensweisen , die miteinander kommunizieren / Objekte, die Dienste für einander bereitstellen , anstelle von Klassen und Daten .

Auf diese Weise haben Sie einige Objekte, die Dienste bereitstellen. Sie verwenden sie als Bausteine ​​(im Grunde genommen wie Lego), um anderes Verhalten zu ermöglichen (z. B. verwendet ein UserRegistrationService die Dienste, die von einem EmailerService und einem UserRepository bereitgestellt werden ).

Bei der Erstellung größerer Systeme mit diesem Ansatz habe ich in sehr wenigen Fällen natürlich die Vererbung verwendet. Letztendlich ist die Vererbung ein sehr mächtiges Werkzeug, das mit Vorsicht und nur bei Bedarf eingesetzt werden sollte.


Ich sehe die Java-Mentalität hier. In OOP geht es darum, dass Daten zusammen mit den Funktionen, die darauf wirken, gepackt werden - was Sie beschreiben, wird besser als "Module" bezeichnet. Sie haben Recht damit, dass das Vermeiden der Vererbung eine gute Idee ist, wenn Sie Objekte als Module wiederverwenden, aber ich finde es störend, dass Sie dies anscheinend als bevorzugte Methode zur Verwendung von Objekten empfehlen.
Brilliand

1
@Brilliand Wenn Sie nur wüssten, wie viel Java-Code in dem von Ihnen beschriebenen Stil geschrieben ist und wie viel Horror das ist ... Smalltalk hat den Begriff OOP eingeführt und wurde für Objekte entwickelt, die Nachrichten empfangen. : "Computing sollte als eine inhärente Funktion von Objekten angesehen werden, die durch das Senden von Nachrichten einheitlich aufgerufen werden können." Es werden keine Daten und Funktionen erwähnt, die damit arbeiten. Entschuldigung, aber meiner Meinung nach irren Sie sich ernsthaft. Wenn es nur um Daten und Funktionen ginge, könnte man gerne mit dem Schreiben von Strukturen fortfahren.
Marstato

@ Tsar leider, obwohl Java statische Methoden unterstützt, die Java-Kultur nicht
k_g

@Brilliand Wen interessiert es, worum es bei OOP geht? Was zählt, ist, wie Sie es verwenden können und die Ergebnisse seiner Verwendung auf diese Weise.
user253751

1
Nach der Diskussion mit @Tsar: Java-Klassen müssen nicht instanziiert werden, und Klassen wie UserRegistrationService und EmailService sollten in der Regel nicht instanziiert werden. Klassen ohne zugehörige Daten eignen sich am besten als statische Klassen (und sind daher für das Original irrelevant) Frage). Ich werde einige meiner vorherigen Kommentare löschen, aber meine ursprüngliche Kritik an Marsatos Antwort bleibt bestehen.
Brilliand

4

"Komposition gegenüber Vererbung bevorzugen" ist nur eine gute Heuristik

Sie sollten den Kontext berücksichtigen, da dies keine universelle Regel ist. Nehmen Sie das nicht so, als würden Sie niemals Vererbung verwenden, wenn Sie Kompositionen erstellen können. In diesem Fall können Sie das Problem beheben, indem Sie die Vererbung verbieten.

Ich hoffe, dass dieser Punkt in diesem Beitrag geklärt wird.

Ich werde nicht versuchen, die Verdienste der Komposition allein zu verteidigen. Das halte ich nicht für ein Thema. Stattdessen werde ich auf einige Situationen eingehen, in denen Entwickler eine Vererbung in Betracht ziehen, bei der die Komposition besser geeignet ist. In diesem Sinne hat die Vererbung ihre eigenen Vorzüge, die ich auch als nicht thematisch betrachte.


Fahrzeugbeispiel

Ich schreibe über Entwickler, die versuchen, Dinge auf dumme Weise zu erzählen

Lassen Sie uns für eine Variante der klassischen Beispiele gehen , dass einige OOP Kurse nutzen ... Wir haben eine VehicleKlasse, dann leiten wir Car, Airplane, BalloonundShip .

Hinweis : Wenn Sie dieses Beispiel erden müssen, tun Sie so, als wären dies Objekte in einem Videospiel.

Dann Carund Airplanevielleicht haben sie einen gemeinsamen Code, weil sie beide an Land auf dem Rad rollen können. Die Entwickler können in Betracht ziehen, dafür eine Zwischenklasse in der Vererbungskette zu erstellen. Tatsächlich gibt es aber auch einen gemeinsamen Code zwischen Airplaneund Balloon. Sie könnten in Betracht ziehen, dafür eine weitere Zwischenklasse in der Vererbungskette zu erstellen.

Daher würde der Entwickler eine Mehrfachvererbung anstreben. An dem Punkt, an dem die Entwickler nach Mehrfachvererbung suchen, ist das Design bereits schiefgelaufen.

Es ist besser, dieses Verhalten als Schnittstellen und Komposition zu modellieren, damit wir es wiederverwenden können, ohne auf die Vererbung mehrerer Klassen stoßen zu müssen. Wenn die Entwickler beispielsweise die FlyingVehiculeKlasse erstellen . Sie würden sagen, dass dies Airplaneeine FlyingVehicule(Klassenvererbung) ist, aber wir könnten stattdessen sagen, dass dies Airplaneeine FlyingKomponente (Zusammensetzung) und Airplaneeine IFlyingVehicule(Schnittstellenvererbung) ist.

Falls erforderlich, können wir Schnittstellen mehrfach vererben (von Schnittstellen). Darüber hinaus koppeln Sie nicht an eine bestimmte Implementierung. Verbesserte Wiederverwendbarkeit und Testbarkeit Ihres Codes.

Denken Sie daran, dass Vererbung ein Werkzeug für Polymorphismus ist. Darüber hinaus ist Polymorphismus ein Werkzeug für die Wiederverwendbarkeit. Wenn Sie die Wiederverwendbarkeit Ihres Codes mithilfe der Komposition erhöhen können, tun Sie dies. Wenn Sie sich nicht sicher sind, ob die Komposition eine bessere Wiederverwendbarkeit bietet, ist "Komposition der Vererbung vorziehen" eine gute Heuristik.

Das alles ohne zu erwähnen Amphibious.

Tatsächlich brauchen wir möglicherweise keine Dinge, die vom Boden abgehen. Stephen Hurn hat ein beredteres Beispiel in seinen Artikeln "Favor Composition Over Inheritance" Teil 1 und Teil 2 .


Substituierbarkeit und Verkapselung

Soll Aerben oder komponieren B?

Wenn Aeine Spezialisierung davon Bdas Liskov-Substitutionsprinzip erfüllen soll, ist eine Vererbung möglich oder sogar wünschenswert. Wenn es Situationen gibt, in denen Akein gültiger Ersatz für vorhanden Bist, sollten wir keine Vererbung verwenden.

Wir könnten an Komposition als Form der defensiven Programmierung interessiert sein, um die abgeleitete Klasse zu verteidigen . Insbesondere wenn Sie mit der Verwendung Bfür andere Zwecke beginnen, besteht möglicherweise der Druck, sie zu ändern oder zu erweitern, um sie für diese Zwecke besser geeignet zu machen. Wenn das Risiko besteht, dass BMethoden offengelegt werden, die zu einem ungültigen Status führen können A, sollten wir Komposition anstelle von Vererbung verwenden. Auch wenn wir der Autor von beidem sind Bund Aes eine Sache weniger ist, sich Sorgen zu machen, erleichtert die Komposition die Wiederverwendbarkeit von B.

Wir können sogar argumentieren, dass, wenn es Features gibt B, Adie nicht benötigt werden (und wir nicht wissen, ob diese Features zu einem ungültigen Status für führen könntenA es eine gute Idee ist, Komposition zu verwenden , weder in der gegenwärtigen noch in der Zukunft) statt vererbung.

Die Komposition hat auch die Vorteile, dass sie das Umschalten von Implementierungen ermöglicht und das Verspotten erleichtert.

Hinweis : Es gibt Situationen, in denen wir Komposition verwenden möchten, obwohl die Ersetzung gültig ist. Wir archivieren diese Substituierbarkeit mithilfe von Interfaces oder abstrakten Klassen (welche verwendet werden sollen, wenn es sich um ein anderes Thema handelt) und verwenden dann die Komposition mit Abhängigkeitsinjektion der realen Implementierung.

Schließlich gibt es natürlich das Argument, dass wir Komposition verwenden sollten , um die übergeordnete Klasse zu verteidigen, da die Vererbung die Kapselung der übergeordneten Klasse unterbricht:

Durch die Vererbung wird eine Unterklasse den Details der Implementierung der übergeordneten Klasse ausgesetzt. Es wird häufig gesagt, dass durch die Vererbung die Kapselung unterbrochen wird.

- Entwurfsmuster: Elemente wiederverwendbarer objektorientierter Software, Vierergruppe

Nun, das ist eine schlecht designte Elternklasse. Welches ist, warum Sie sollten:

Entwerfen Sie für die Vererbung oder verbieten Sie es.

- Wirksames Java, Josh Bloch


Jo-Jo-Problem

Ein weiterer Fall, in dem Komposition hilft, ist das Jo-Jo-Problem . Dies ist ein Zitat aus Wikipedia:

In der Softwareentwicklung ist das Jojo-Problem ein Anti-Muster, das auftritt, wenn ein Programmierer ein Programm lesen und verstehen muss, dessen Vererbungsgraph so lang und kompliziert ist, dass der Programmierer ständig zwischen vielen verschiedenen Klassendefinitionen wechseln muss, um zu folgen der Kontrollfluss des Programms.

Sie können beispielsweise Folgendes auflösen: Ihre Klasse Cwird nicht von der Klasse erben B. Stattdessen hat Ihre Klasse Cein Element vom Typ A, das ein Objekt vom Typ sein kann oder nicht B. Auf diese Weise programmieren Sie nicht mit dem Implementierungsdetail von B, sondern mit dem Vertrag, den die Schnittstelle (von) Aanbietet.


Gegenbeispiele

Viele Frameworks bevorzugen die Vererbung gegenüber der Komposition (was das Gegenteil von dem ist, was wir argumentiert haben). Entwickler tun dies möglicherweise, weil sie viel Arbeit in ihre Basisklasse stecken, weil die Implementierung mit Komposition die Größe des Clientcodes erhöht hätte. Manchmal liegt dies an sprachlichen Einschränkungen.

Beispielsweise kann ein PHP-ORM-Framework eine Basisklasse erstellen, die mithilfe magischer Methoden das Schreiben von Code ermöglicht, als ob das Objekt echte Eigenschaften hätte. Stattdessen wird der Code, der von den Magic-Methoden verarbeitet wird, in die Datenbank verschoben, nach dem bestimmten angeforderten Feld abgefragt (möglicherweise für zukünftige Anforderungen zwischengespeichert) und zurückgegeben. Bei der Komposition muss der Client entweder Eigenschaften für jedes Feld erstellen oder eine Version des Codes für magische Methoden schreiben.

Nachtrag : Es gibt noch einige andere Möglichkeiten, ORM-Objekte zu erweitern. Daher halte ich eine Vererbung in diesem Fall nicht für erforderlich. Es ist günstiger.

In einem anderen Beispiel kann eine Videospiel-Engine eine Basisklasse erstellen, die je nach Zielplattform systemeigenen Code verwendet, um 3D-Rendering und Ereignisbehandlung durchzuführen. Dieser Code ist komplex und plattformspezifisch. Es wäre teuer und fehleranfällig für den Entwickler des Moduls, sich mit diesem Code zu befassen, was in der Tat ein Grund für die Verwendung des Moduls ist.

Darüber hinaus funktionieren ohne den 3D-Rendering-Teil so viele Widget-Frameworks. Dies befreit Sie von der Sorge, mit Betriebssystemnachrichten umzugehen. In vielen Sprachen können Sie keinen solchen Code schreiben, ohne eine Form von systemeigenem Gebot zu haben. Wenn Sie dies tun würden, würde dies außerdem Ihre Portabilität beeinträchtigen. Stattdessen mit Vererbung, vorausgesetzt, der Entwickler unterbricht die Kompatibilität nicht (zu stark); Sie können Ihren Code problemlos auf neue Plattformen portieren, die sie in Zukunft unterstützen.

Bedenken Sie außerdem, dass wir häufig nur einige Methoden überschreiben und alles andere mit den Standardimplementierungen belassen möchten. Wenn wir Komposition verwendet hätten, müssten wir alle diese Methoden erstellen, auch wenn wir nur an das umschlossene Objekt delegieren würden.

Durch dieses Argument gibt es einen Punkt, an dem die Komposition für die Wartbarkeit schlechter ist als die Vererbung (wenn die Basisklasse zu komplex ist). Denken Sie jedoch daran, dass die Wartbarkeit der Vererbung schlechter sein kann als die der Komposition (wenn der Vererbungsbaum zu komplex ist), worauf ich im Jojo-Problem Bezug nehme.

In den vorgestellten Beispielen beabsichtigen die Entwickler selten, den durch Vererbung generierten Code in anderen Projekten wiederzuverwenden. Dies verringert die verringerte Wiederverwendbarkeit der Verwendung von Vererbung anstelle von Komposition. Durch die Verwendung der Vererbung können die Framework-Entwickler außerdem eine Menge einfach zu verwendenden und leicht zu erkennenden Codes bereitstellen.


Abschließende Gedanken

Wie Sie sehen, hat die Komposition in einigen Situationen, nicht immer, einen gewissen Vorteil gegenüber der Vererbung. Es ist wichtig, den Kontext und die verschiedenen Faktoren (wie Wiederverwendbarkeit, Wartbarkeit, Testbarkeit usw.) zu berücksichtigen, um die Entscheidung zu treffen. Zurück zum ersten Punkt: „Bevorzugen Komposition der Vererbung“ ist eine nur gute Heuristik.

Möglicherweise stellen Sie auch fest, dass viele der von mir beschriebenen Situationen bis zu einem gewissen Grad mit Traits oder Mixins gelöst werden können. Leider sind diese Funktionen in der großen Liste der Sprachen nicht alltäglich und verursachen in der Regel einige Leistungskosten. Zum Glück entschärfen ihre beliebten Erweiterungsmethoden und Standardimplementierungen einige Situationen.

Ich habe kürzlich einen Beitrag veröffentlicht, in dem ich einige der Vorteile von Schnittstellen erläutere. Warum benötigen wir Schnittstellen zwischen Benutzeroberfläche, Unternehmen und Datenzugriff in C # ? Es hilft beim Entkoppeln und erleichtert die Wiederverwendbarkeit und Testbarkeit.


Äh ... .Net Framework verwendet verschiedene Arten von geerbten Streams, um seine Stream-bezogene Arbeit zu erledigen. Es funktioniert unglaublich gut und ist super einfach zu bedienen und zu erweitern. Ihr Beispiel ist nicht sehr gut ...
T. Sar - Reinstate Monica

1
@TSar "es ist super einfach zu bedienen und zu erweitern" Haben Sie jemals versucht, den Standard-Out-Stream auf Debug zu bringen? Das war meine einzige Erfahrung beim Versuch, ihre Streams zu erweitern. Es war nicht einfach. Es war ein Albtraum, der dazu führte, dass ich jede einzelne Methode in der Klasse explizit überschrieb. Sehr repetitiv. Und wenn Sie sich den .NET-Quellcode ansehen, ist es ziemlich genau so, wie sie es tun, wenn Sie alles explizit überschreiben. Daher bin ich nicht davon überzeugt, dass es jemals so einfach ist, es zu erweitern.
jpmc26

Das Lesen und Schreiben einer Struktur aus einem Stream erfolgt besser mit freien Funktionen oder Multimethoden.
user253751

@ jpmc26 Es tut mir leid, dass deine Erfahrung nicht sehr gut war. Ich hatte jedoch keine Probleme damit, Stream-bezogene Dinge für verschiedene Dinge zu verwenden oder zu implementieren.
T. Sar - Reinstate Monica

3

Normalerweise besteht die Grundidee von Composition-Over-Inheritance darin, eine größere Entwurfsflexibilität bereitzustellen und nicht die Menge an Code zu reduzieren, die Sie ändern müssen, um eine Änderung zu propagieren (was ich fragwürdig finde).

Das Beispiel auf Wikipedia gibt ein besseres Beispiel für die Kraft seines Ansatzes:

Wenn Sie für jedes "Kompositionsstück" eine Basisschnittstelle definieren, können Sie auf einfache Weise verschiedene "Kompositionsobjekte" mit einer Vielfalt erstellen, die nur schwer mit der Vererbung zu erreichen ist.


So dies gibt Abschnitt eine Zusammensetzung Beispiel, nicht wahr? Ich frage, weil es im Artikel nicht offensichtlich ist.
Utku

1
Ja, "Komposition über Vererbung" bedeutet nicht, dass Sie keine Schnittstellen haben. Wenn Sie sich das Player-Objekt ansehen, sehen Sie, dass es gut in eine Hierarchie passt. aber aus der komposition mit einer klasse, die die
arbeit

2

Die Zeile "Komposition vor Vererbung" ist nichts anderes als ein Schubs in die richtige Richtung. Es wird dich nicht vor deiner eigenen Dummheit bewahren und gibt dir die Chance, die Dinge noch viel schlimmer zu machen. Das liegt daran, dass hier viel Kraft steckt.

Der Hauptgrund dafür ist, dass Programmierer faul sind. So faul, dass sie jede Lösung wählen, die weniger Tastatureingaben bedeutet. Das ist das Hauptverkaufsargument für Vererbung. Ich tippe heute weniger und morgen wird jemand anderes geschraubt. Also, ich will nach Hause gehen.

Am nächsten Tag besteht der Chef darauf, dass ich Komposition benutze. Erklärt nicht warum, besteht aber darauf. Ich nehme also eine Instanz von dem, von dem ich geerbt habe, lege eine Schnittstelle offen, die mit der Schnittstelle identisch ist, von der ich geerbt habe, und implementiere diese Schnittstelle, indem ich die gesamte Arbeit an das Objekt delegiere, von dem ich geerbt habe. Es ist alles hirntote Eingabe, die mit einem Refactoring-Tool hätte gemacht werden können und mir sinnlos erscheint, aber der Chef wollte es, also mache ich es.

Am nächsten Tag frage ich, ob dies das ist, was gesucht wurde. Was glaubst du, sagt der Chef?

Es war eine enorme Zeitverschwendung, es sei denn, es bestand die Notwendigkeit, dynamisch (zur Laufzeit) zu ändern, von was geerbt wurde (siehe Statusmuster). Wie kann der Chef das sagen und trotzdem für eine Komposition vor einer Vererbung sein?

Zusätzlich dazu, dass dies nicht dazu beiträgt, einen Bruch aufgrund von Änderungen der Methodensignatur zu verhindern, wird der größte Vorteil der Komposition gänzlich übersehen. Im Gegensatz zur Vererbung können Sie eine andere Schnittstelle erstellen . Eine, die besser dazu passt, wie es auf dieser Ebene verwendet wird. Weißt du, Abstraktion!

Das automatische Ersetzen der Vererbung durch Komposition und Delegierung hat einige kleinere Vorteile (und einige Nachteile), aber wenn Sie Ihr Gehirn während dieses Vorgangs ausgeschaltet lassen, verpassen Sie eine große Chance.

Indirektion ist sehr mächtig. Benutze es weise.


0

Hier ist ein weiterer Grund: In vielen OO-Sprachen (ich denke hier an solche wie C ++, C # oder Java) gibt es keine Möglichkeit, die Klasse eines Objekts zu ändern, nachdem Sie es erstellt haben.

Angenommen, wir erstellen eine Mitarbeiterdatenbank. Wir haben eine abstrakte Basisklasse für Mitarbeiter und beginnen, neue Klassen für bestimmte Rollen abzuleiten - Ingenieur, Wachmann, LKW-Fahrer und so weiter. Jede Klasse hat ein spezielles Verhalten für diese Rolle. Alles ist gut.

Dann wird eines Tages ein Ingenieur zum Engineering Manager befördert. Sie können einen vorhandenen Ingenieur nicht neu klassifizieren. Stattdessen müssen Sie eine Funktion schreiben, die einen Engineering Manager erstellt, alle Daten aus dem Engineer kopiert, den Engineer aus der Datenbank löscht und dann den neuen Engineering Manager hinzufügt. Und Sie müssen eine solche Funktion für jeden möglichen Rollenwechsel schreiben.

Schlimmer noch: Angenommen, ein Lkw-Fahrer ist langfristig krankgeschrieben, und ein Wachmann bietet an, in Teilzeit Lkw-Fahren zu absolvieren. Jetzt müssen Sie eine neue Klasse für Wachmann und Lkw-Fahrer erfinden.

Es macht das Leben so viel einfacher, eine konkrete Employee-Klasse zu erstellen und sie als Container zu behandeln, dem Sie Eigenschaften wie die Jobrolle hinzufügen können.


Oder Sie erstellen eine Employee-Klasse mit einem Zeiger auf eine Rollenliste und jeder tatsächliche Job erweitert die Rolle. Ihr Beispiel könnte sein, die Vererbung auf eine sehr gute und vernünftige Art und Weise zu nutzen, ohne zu viel Arbeit zu haben.
T. Sar - Reinstate Monica

0

Vererbung bietet zwei Dinge:

1) Code-Wiederverwendung: Kann leicht durch Komposition erreicht werden. Tatsächlich wird dies besser durch Komposition erreicht, da die Einkapselung besser erhalten bleibt.

2) Polymorphismus: Kann durch "Interfaces" (Klassen, in denen alle Methoden rein virtuell sind) erreicht werden.

Ein starkes Argument für die Verwendung der Nicht-Schnittstellenvererbung ist, wenn die Anzahl der erforderlichen Methoden groß ist und Ihre Unterklasse nur eine kleine Teilmenge von ihnen ändern möchte. Im Allgemeinen sollte Ihre Benutzeroberfläche jedoch nur einen sehr geringen Platzbedarf haben, wenn Sie sich dem Prinzip der "Einzelverantwortung" anschließen (Sie sollten dies tun). Daher sollten diese Fälle selten vorkommen.

Der Codierungsstil, der das Überschreiben aller Methoden der übergeordneten Klasse erfordert, vermeidet es nicht, eine große Anzahl von Passthrough-Methoden zu schreiben. Warum also nicht Code durch Komposition wiederverwenden und den zusätzlichen Vorteil der Kapselung nutzen? Wenn die Superklasse durch Hinzufügen einer weiteren Methode erweitert würde, müsste diese Methode für den Codierungsstil allen Unterklassen hinzugefügt werden (und es besteht eine gute Chance, dass wir dann gegen die "Schnittstellentrennung" verstoßen ). Dieses Problem wächst schnell, wenn Sie mehrere Ebenen in der Vererbung haben.

Schließlich ist es in vielen Sprachen viel einfacher, auf "Komposition" basierende Objekte auf Unit-Tests zu testen, als auf "Vererbung" basierende Objekte auf Unit-Tests, indem das Mitgliedsobjekt durch ein Schein- / Test- / Dummy-Objekt ersetzt wird.


0

Ist das wirklich der einzige Grund, die Komposition der Vererbung vorzuziehen?

Nee.

Es gibt viele Gründe, die Zusammensetzung der Vererbung vorzuziehen. Es gibt keine einzige richtige Antwort. Aber ich werde der Mischung einen anderen Grund vorziehen, Komposition gegenüber Vererbung zu bevorzugen:

Durch die Bevorzugung der Komposition gegenüber der Vererbung wird die Lesbarkeit Ihres Codes verbessert ist sehr wichtig, wenn Sie an einem großen Projekt mit mehreren Personen arbeiten.

So denke ich darüber nach: Wenn ich den Code einer anderen Person lese und sehe, dass sie eine Klasse erweitert hat, werde ich sie fragen welches Verhalten in der Superklasse sie ändert.

Und dann gehe ich den Code durch und suche nach einer Überschreibungsfunktion. Wenn ich keine sehe, klassifiziere ich das als Missbrauch der Vererbung. Sie hätten stattdessen Kompositionen verwenden sollen, um mich (und jede andere Person im Team) vor 5 Minuten Lesen des Codes zu bewahren!

Hier ein konkretes Beispiel: In Java JFramerepräsentiert die Klasse ein Fenster. Um ein Fenster zu erstellen, erstellen Sie eine Instanz von JFrameund rufen dann Funktionen für diese Instanz auf, um ihr Inhalte hinzuzufügen und sie anzuzeigen.

In vielen Tutorials (auch den offiziellen) wird empfohlen, diese zu erweitern, JFramedamit Sie Instanzen Ihrer Klasse als Instanzen von behandeln können JFrame. Aber imho (und die Meinung vieler anderer) ist, dass es eine ziemlich schlechte Angewohnheit ist, sich darauf einzulassen! Stattdessen sollten Sie nur eine Instanz von erstellen JFrameund Funktionen für diese Instanz aufrufen. JFrameIn diesem Fall ist eine Erweiterung absolut nicht erforderlich , da Sie das Standardverhalten der übergeordneten Klasse nicht ändern.

Auf der anderen Seite besteht eine Möglichkeit zum benutzerdefinierten Zeichnen darin, die JPanelKlasse zu erweitern und die paintComponent()Funktion zu überschreiben . Dies ist eine gute Verwendung der Vererbung. Wenn ich Ihren Code durchsuche und sehe, dass Sie JPaneldie paintComponent()Funktion erweitert und überschrieben haben , weiß ich genau, welches Verhalten Sie ändern.

Wenn Sie nur Code selbst schreiben, ist die Verwendung der Vererbung möglicherweise genauso einfach wie die Verwendung der Komposition. Stellen Sie sich jedoch vor, Sie befinden sich in einer Gruppenumgebung und andere Personen lesen Ihren Code. Durch die Bevorzugung der Komposition gegenüber der Vererbung wird der Code leichter lesbar.


Das Hinzufügen einer gesamten Factory-Klasse mit einer einzigen Methode, die eine Instanz eines Objekts zurückgibt, sodass Sie Komposition anstelle von Vererbung verwenden können, verbessert die Lesbarkeit des Codes nicht wirklich ... (Ja, dies ist erforderlich, wenn Sie jedes Mal eine neue Instanz benötigen eine bestimmte Methode wird aufgerufen.) Das bedeutet nicht, dass die Vererbung gut ist, aber die Komposition verbessert nicht immer die Lesbarkeit.
jpmc26

1
@ jpmc26 ... warum um alles in der Welt brauchen Sie eine Factory-Klasse, um eine neue Instanz einer Klasse zu erstellen? Und wie verbessert die Vererbung die Lesbarkeit Ihrer Beschreibung?
Kevin Workman

Habe ich Ihnen im obigen Kommentar nicht ausdrücklich einen Grund für eine solche Fabrik genannt? Es ist besser lesbar, da weniger Code zu lesen ist . Sie können dasselbe Verhalten mit einer einzelnen abstrakten Methode in der Basisklasse und einigen Unterklassen erzielen. (Sie hatten ohnehin eine andere Implementierung Ihrer zusammengesetzten Klasse für jede.) Wenn die Details der Konstruktion des Objekts stark an die Basisklasse gebunden sind, wird es an keiner anderen Stelle in Ihrem Code Verwendung finden, was zu einer weiteren Reduzierung führt die Vorteile einer separaten Klasse. Dann hält es eng verwandte Teile des Codes nahe beieinander.
jpmc26

Wie ich bereits sagte, bedeutet dies nicht unbedingt, dass die Vererbung gut ist, aber es zeigt exemplarisch, dass die Komposition in manchen Situationen die Lesbarkeit nicht verbessert.
jpmc26

1
Ohne ein konkretes Beispiel zu sehen, ist es schwer wirklich zu kommentieren. Aber was Sie beschrieben haben, klingt für mich wie ein Fall von Überentwicklung. Benötigen Sie eine Instanz einer Klasse? Das newSchlüsselwort gibt Ihnen einen. Trotzdem danke für das Gespräch.
Kevin Workman
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.