Frage Zusammensetzung vor Vererbung bevorzugen?


Warum bevorzugen Sie die Zusammensetzung über die Vererbung? Welche Kompromisse gibt es für jeden Ansatz? Wann sollten Sie Vererbung über Komposition wählen?


1348
2017-09-08 01:58


Ursprung


Antworten:


Bevorzugen Sie die Komposition über die Vererbung, da sie später besser formbar / einfach zu modifizieren ist, verwenden Sie jedoch nicht den Ansatz "Immer komponieren". Mit der Zusammensetzung ist es einfach, das Verhalten mit Dependency Injection / Setter zu ändern. Die Vererbung ist starrer, da es die meisten Sprachen nicht erlauben, von mehr als einem Typ abzuleiten. So wird die Gans mehr oder weniger gekocht, sobald Sie von TypeA abgeleitet sind.

Mein Härtetest für das Obige ist:

  • Will TypeB die vollständige Schnittstelle (alle öffentlichen Methoden nicht weniger) von TypeA verfügbar machen, so dass TypeB verwendet werden kann, wo TypeA erwartet wird? Zeigt an Erbe.

z.B. Ein Cessna-Doppeldecker wird die komplette Schnittstelle eines Flugzeugs freilegen, wenn nicht mehr. Das macht es passend, aus dem Flugzeug zu kommen.

  • Will TypeB nur einen Teil des Verhaltens von TypeA verfügbar machen? Zeigt an, dass Bedarf besteht Zusammensetzung. 

z.B. Ein Vogel benötigt möglicherweise nur das Flugverhalten eines Flugzeugs. In diesem Fall ist es sinnvoll, es als Schnittstelle / Klasse / beides zu extrahieren und es zu einem Mitglied beider Klassen zu machen.

Aktualisieren: Ich bin gerade auf meine Antwort zurückgekommen und es scheint, dass sie unvollständig ist, ohne eine besondere Erwähnung von Barbara Liskov Liskow-Substitutionsprinzip als Test für "Sollte ich von diesem Typ erben?"


1010
2017-09-10 03:04



Denken Sie an Eindämmung als hat ein Beziehung. Ein Auto hat einen Motor, eine Person hat einen Namen usw.

Denken Sie an Vererbung als ist ein Beziehung. Ein Auto "ist ein" Fahrzeug, eine Person "ist ein" Säugetier, etc.

Ich akzeptiere diesen Ansatz nicht. Ich nahm es direkt von der Zweite Ausgabe des Codes abgeschlossen durch Steve McConnell, Abschnitt 6.3.


347
2017-09-08 02:09



Wenn Sie den Unterschied verstehen, ist es einfacher zu erklären.

Verfahrenscode

Ein Beispiel hierfür ist PHP ohne die Verwendung von Klassen (besonders vor PHP5). Alle Logik ist in einer Reihe von Funktionen codiert. Sie können andere Dateien mit Hilfsfunktionen usw. hinzufügen und Ihre Geschäftslogik ausführen, indem Sie Daten in Funktionen übergeben. Dies kann sehr schwer zu verwalten sein, wenn die Anwendung wächst. PHP5 versucht dies zu beheben, indem mehr objektorientiertes Design angeboten wird.

Erbe

Dies fördert die Verwendung von Klassen. Vererbung ist eine der drei Säulen des OO-Designs (Vererbung, Polymorphie, Verkapselung).

class Person {
   String Title;
   String Name;
   Int Age
}

class Employee : Person {
   Int Salary;
   String Title;
}

Dies ist Vererbung bei der Arbeit. Der Mitarbeiter ist eine Person oder erbt von einer Person. Alle Vererbungsbeziehungen sind Beziehungen "ist-a". Der Mitarbeiter überschattet auch die Titeleigenschaft von Person, was bedeutet, dass Mitarbeiter.Titel den Titel für den Mitarbeiter und nicht die Person zurückgibt.

Zusammensetzung

Zusammensetzung ist gegenüber Vererbung bevorzugt. Um es ganz einfach zu sagen:

class Person {
   String Title;
   String Name;
   Int Age;

   public Person(String title, String name, String age) {
      this.Title = title;
      this.Name = name;
      this.Age = age;
   }

}

class Employee {
   Int Salary;
   private Person person;

   public Employee(Person p, Int salary) {
       this.person = p;
       this.Salary = salary;
   }
}

Person johnny = new Person ("Mr.", "John", 25);
Employee john = new Employee (johnny, 50000);

Die Zusammensetzung ist typischerweise "hat eine" oder "verwendet eine" Beziehung. Hier hat die Mitarbeiterklasse eine Person. Es erbt nicht von Person, sondern erhält stattdessen das Person-Objekt, weshalb es "eine Person" hat.

Zusammensetzung über Vererbung

Nun sagen Sie, dass Sie einen Manager-Typ erstellen möchten, so dass Sie enden mit:

class Manager : Person, Employee {
   ...
}

Dieses Beispiel wird jedoch funktionieren, wenn sowohl Person als auch Mitarbeiter deklariert sind Title? Sollte Manager.Title "Manager of Operations" oder "Mr." zurückgeben? Unter der Zusammensetzung wird diese Mehrdeutigkeit besser gehandhabt:

Class Manager {
   public Title;
   public Manager(Person p, Employee e)
   {
      this.Title = e.Title;
   }
}

Das Manager-Objekt besteht aus einem Mitarbeiter und einer Person. Das Titelverhalten wird vom Mitarbeiter übernommen. Diese explizite Komposition entfernt Mehrdeutigkeiten unter anderem und Sie werden weniger Fehler finden.


175
2018-05-21 07:54



Mit all den unbestreitbaren Vorteilen, die die Vererbung bietet, hier einige ihrer Nachteile.

Nachteile der Vererbung:

  1. Sie können die von Super-Klassen zur Laufzeit geerbte Implementierung nicht ändern (offensichtlich, weil die Vererbung zur Kompilierungszeit definiert ist).
  2. Die Vererbung macht eine Unterklasse für Details der Klassenimplementierung der Eltern verfügbar, weshalb oft gesagt wird, dass Vererbung die Kapselung bricht (in einem Sinne, dass man sich wirklich nur auf Interfaces konzentrieren muss, nicht auf die Implementierung, so dass Wiederverwendung durch Unterklassen nicht immer bevorzugt wird).
  3. Die enge Kopplung, die durch die Vererbung bereitgestellt wird, macht die Implementierung einer Unterklasse sehr eng mit der Implementierung einer Superklasse verbunden, so dass jede Änderung in der übergeordneten Implementierung die Unterklasse zu einer Änderung zwingt.
  4. Übermäßige Wiederverwendung durch Unterklassenbildung kann den Vererbungsstapel sehr tief und sehr verwirrend machen.

Andererseits Objekt-Zusammensetzung wird zur Laufzeit durch Objekte definiert, die Referenzen auf andere Objekte erhalten. In einem solchen Fall werden diese Objekte niemals in der Lage sein, die geschützten Daten des jeweils anderen zu erreichen (keine Verkapselungspause), und sie werden gezwungen sein, die gegenseitige Schnittstelle zu respektieren. Und in diesem Fall werden Implementierungsabhängigkeiten viel weniger sein als im Falle einer Vererbung.


91
2018-05-21 08:34



Ein anderer, sehr pragmatischer Grund, die Komposition der Vererbung vorzuziehen, hat mit Ihrem Domänenmodell zu tun und es einer relationalen Datenbank zuzuordnen. Es ist wirklich schwierig, die Vererbung dem SQL-Modell zuzuordnen (am Ende gibt es alle möglichen heiklen Workarounds, wie das Erstellen von Spalten, die nicht immer verwendet werden, das Verwenden von Ansichten usw.). Einige ORML versuchen, damit umzugehen, aber es wird immer schnell kompliziert. Komposition kann leicht durch eine Fremdschlüsselbeziehung zwischen zwei Tabellen modelliert werden, aber Vererbung ist viel schwieriger.


77
2017-09-08 02:48



Während ich in kurzen Worten zustimmen würde "Lieber Komposition als Vererbung vorziehen", klingt es für mich sehr oft nach "lieber Kartoffeln als Coca-Cola". Es gibt Orte für die Vererbung und Orte für die Zusammensetzung. Sie müssen Unterschiede verstehen, dann wird diese Frage verschwinden. Was es für mich wirklich bedeutet, ist, "wenn Sie Vererbung verwenden werden - denken Sie wieder, Chancen sind Sie brauchen Zusammensetzung".

Sie sollten Kartoffeln über Coca Cola bevorzugen, wenn Sie essen möchten, und Coca Cola über Kartoffeln, wenn Sie trinken wollen.

Das Erstellen einer Unterklasse sollte mehr als nur eine bequeme Methode zum Aufrufen von Oberklassenmethoden sein. Sie sollten die Vererbung verwenden, wenn die Unterklasse "is-a" eine Superklasse ist, sowohl strukturell als auch funktional, wenn sie als Oberklasse verwendet werden kann und Sie diese verwenden werden. Wenn es nicht der Fall ist - es ist keine Vererbung, sondern etwas anderes. Komposition ist, wenn Ihre Objekte aus einem anderen bestehen oder eine Beziehung zu ihnen haben.

So sieht es für mich aus, wenn jemand nicht weiß, ob er Vererbung oder Zusammensetzung braucht, das eigentliche Problem ist, dass er nicht weiß, ob er trinken oder essen möchte. Denke über deine Problemdomäne nach, verstehe sie besser.


64
2018-05-08 22:29



Die Vererbung ist sehr verlockend, vor allem aus dem Bereich der Verfahrenstechnik und sieht oft täuschend elegant aus. Ich meine, alles, was ich tun muss, ist diese ein bisschen Funktionalität zu einer anderen Klasse hinzuzufügen, richtig? Nun, eines der Probleme ist das

Vererbung ist wahrscheinlich die schlimmste Form der Kopplung, die Sie haben können

Ihre Basisklasse bricht die Kapselung ab, indem sie Implementierungsdetails in Form von geschützten Elementen den Unterklassen zugänglich macht. Dies macht Ihr System starr und zerbrechlich. Der tragische Fehler ist jedoch, dass die neue Unterklasse das ganze Gepäck und die Meinung der Erbkette mit sich bringt.

Der Artikel, Vererbung ist böse: Der epische Fehlschlag des DataAnnotationsModelBinder, geht durch ein Beispiel in C #. Es zeigt die Verwendung der Vererbung, wenn die Komposition verwendet werden sollte und wie sie refaktoriert werden könnte.


54
2018-01-23 19:55



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

Wenn Ihr Objekt also als ein anderes Objekt erscheinen oder sich je nach Objektstatus oder -bedingungen anders verhalten soll, dann verwenden Sie Zusammensetzung: Beziehen auf Zustand und Strategie Designmuster.

Wenn das Objekt vom selben Typ sein muss, verwenden Sie Erbe oder implementieren Sie Schnittstellen.


37
2017-09-08 02:25



Ich persönlich habe gelernt, die Komposition immer der Vererbung vorzuziehen. Es gibt kein programmatisches Problem, das Sie mit der Vererbung lösen können, das Sie mit der Komposition nicht lösen können; obwohl Sie in einigen Fällen Schnittstellen (Java) oder Protokolle (Obj-C) verwenden müssen. Da C ++ solche Dinge nicht kennt, müssen Sie abstrakte Basisklassen verwenden, was bedeutet, dass Sie die Vererbung in C ++ nicht vollständig loswerden können.

Komposition ist oft logischer, sie bietet bessere Abstraktion, bessere Kapselung, bessere Codewiederverwendung (besonders in sehr großen Projekten) und ist weniger wahrscheinlich, um etwas aus der Entfernung zu zerstören, nur weil Sie irgendwo in Ihrem Code eine isolierte Änderung vorgenommen haben. Es erleichtert auch die Aufrechterhaltung der "Prinzip der einfachen Verantwortung", die oft als"Es sollte nie mehr als einen Grund geben, dass sich eine Klasse ändert."und es bedeutet, dass jede Klasse für einen bestimmten Zweck existiert und sie nur Methoden haben sollte, die direkt mit ihrem Zweck in Verbindung stehen. Auch eine sehr flache Vererbungsstruktur macht es viel einfacher, den Überblick zu behalten, selbst wenn Ihr Projekt wirklich beginnt Viele Leute denken, dass Vererbung unser repräsentiert echte Welt ziemlich gut, aber das ist nicht die Wahrheit. Die reale Welt verwendet viel mehr Komposition als Vererbung. So ziemlich jedes reale Weltobjekt, das du in deiner Hand halten kannst, wurde aus anderen, kleineren realen Objekten zusammengesetzt.

Es gibt jedoch Nachteile bei der Komposition. Wenn Sie die Vererbung komplett überspringen und sich nur auf die Komposition konzentrieren, werden Sie feststellen, dass Sie oft ein paar zusätzliche Codezeilen schreiben müssen, die nicht notwendig waren, wenn Sie die Vererbung verwendet haben. Sie sind manchmal auch gezwungen, sich zu wiederholen und dies verstößt gegen die Trockenes Prinzip (DRY = Wiederhole dich nicht). Auch die Zusammensetzung erfordert häufig die Delegierung, und eine Methode ruft nur eine andere Methode eines anderen Objekts auf, wobei kein anderer Code diesen Aufruf umgibt. Solche "doppelten Methodenaufrufe" (die leicht auf dreifache oder vierfache Methodenaufrufe und sogar noch weiter ausgedehnt werden können) haben eine viel schlechtere Leistung als die Vererbung, bei der Sie einfach eine Methode Ihres Elternteils erben. Das Aufrufen einer geerbten Methode kann genauso schnell wie das Aufrufen einer nicht vererbten Methode sein, oder sie kann etwas langsamer sein, ist aber in der Regel immer noch schneller als zwei aufeinanderfolgende Methodenaufrufe.

Sie haben vielleicht bemerkt, dass die meisten OO-Sprachen keine Mehrfachvererbung zulassen. Es gibt zwar einige Fälle, in denen die Mehrfachvererbung wirklich etwas kaufen kann, aber das sind eher Ausnahmen als die Regel. Wann immer Sie in eine Situation geraten, in der Sie denken, dass "mehrfache Vererbung ein wirklich cooles Feature zur Lösung dieses Problems wäre", sind Sie normalerweise an einem Punkt, an dem Sie die Vererbung insgesamt neu überdenken sollten, da selbst einige zusätzliche Codezeilen erforderlich sind Eine auf Komposition basierende Lösung wird sich in der Regel als wesentlich eleganter, flexibler und zukunftssicher erweisen.

Vererbung ist wirklich ein cooles Feature, aber ich fürchte, es wurde in den letzten Jahren überstrapaziert. Die Leute behandelten die Vererbung als den einen Hammer, der alles nageln kann, egal ob es sich tatsächlich um einen Nagel, eine Schraube oder vielleicht etwas ganz anderes handelte.


27
2018-01-31 19:34



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

Um zu verstehen, warum "bevorzugen Komposition über Vererbung ", müssen wir zuerst die in diesem verkürzten Idiom weggelassene Annahme zurückholen.

Es gibt zwei Vorteile der Vererbung: Untertypen und Unterklassen

  1. Untertypen Mittel, die einer Typ- (Schnittstellen-) Signatur entsprechen, d. h. eine Menge von APIs, und man kann einen Teil der Signatur überschreiben, um einen Subtyp-Polymorphismus zu erreichen.

  2. Unterklassifizierung bedeutet implizite Wiederverwendung von Methodenimplementierungen.

Mit den beiden Vorteilen ergeben sich zwei unterschiedliche Zwecke für die Vererbung: subtyping-orientiert und code-reuse-orientiert.

Wenn Codewiederverwendung ist der Sohle, einzig, alleinig Zweck, Subclassing kann mehr als das geben, was er benötigt, d.h. einige öffentliche Methoden der Elternklasse ergeben für die Kindklasse wenig Sinn. In diesem Fall wird die Komposition statt der Vererbung bevorzugt gefordert. Hier kommt auch die "is-a" vs. "has-a" Vorstellung her.

Nur wenn das Subtyping beabsichtigt ist, d. H. Um die neue Klasse später in einer polymorphen Weise zu verwenden, stehen wir vor dem Problem, die Vererbung oder Zusammensetzung zu wählen. Dies ist die Annahme, die in der besprochenen verkürzten Redewendung ausgelassen wird.

Um dem Subtyp eine Typ-Signatur zu geben, bedeutet dies, dass die Komposition immer so viele APIs dieses Typs verfügbar machen muss. Jetzt treten die Trade Offs ein:

  1. Vererbung bietet eine einfache Codewiederverwendung, wenn sie nicht überschrieben wird, während die Komposition jede API neu codieren muss, auch wenn es sich nur um eine einfache Aufgabe der Delegierung handelt.

  2. Vererbung bietet direkte offene Rekursion über die interne polymorphe Stelle this, d. h. Aufruf der Überschreibungsmethode (oder sogar Art) in einer anderen Mitgliedsfunktion, entweder öffentlich oder privat (obwohl entmutigt). Offene Rekursion kann sein simuliert über Komposition, aber es erfordert zusätzlichen Aufwand und möglicherweise nicht immer lebensfähig (?). Dies Antworten zu einer duplizierten Frage spricht etwas Ähnliches.

  3. Vererbung macht verfügbar geschützt Mitglieder. Dadurch wird die Kapselung der übergeordneten Klasse aufgehoben, und wenn sie von der Unterklasse verwendet wird, wird eine weitere Abhängigkeit zwischen dem untergeordneten Element und dem übergeordneten Element eingeführt.

  4. Die Zusammensetzung hat die Fähigkeit zur Inversion der Kontrolle, und ihre Abhängigkeit kann dynamisch injiziert werden, wie in Dekorationsmuster und Proxy-Muster.

  5. Zusammensetzung hat den Vorteil von kombinatororientiert Programmieren, d. h. in einer Weise arbeiten wie die zusammengesetztes Muster.

  6. Die Zusammensetzung folgt unmittelbar Programmierung an einer Schnittstelle.

  7. Zusammensetzung hat den Vorteil von einfach Mehrfachvererbung.

Mit den oben genannten Kompromissen denken wir daher bevorzugen Zusammensetzung über Vererbung. Für eng verwandte Klassen, d. H. Wenn implizite Codewiederverwendung wirklich Vorteile bringt oder die magische Kraft offener Rekursion erwünscht ist, sollte die Vererbung die Wahl sein.


26
2017-09-14 05:22



Meine allgemeine Faustregel: Bevor Sie die Vererbung verwenden, sollten Sie überlegen, ob die Komposition sinnvoller ist.

Grund: Subklassifizieren bedeutet normalerweise mehr Komplexität und Verbundenheit, d. H. Schwieriger zu ändern, zu pflegen und zu skalieren, ohne Fehler zu machen.

Ein viel vollständiger und konkreter Antwort von Tim Boudreau der Sonne:

Häufige Probleme bei der Verwendung von Vererbung, wie ich es sehe sind:

  • Unschuldige Handlungen können zu unerwarteten Ergebnissen führen - Das klassische Beispiel hierfür sind Aufrufe von überschreibbaren Methoden aus der Oberklasse   Konstruktor, bevor die Unterklassen Instanzfelder wurden   initialisiert. In einer perfekten Welt würde das niemand tun. Das ist   keine perfekte Welt.
  • Es bietet perverse Versuchungen für Unterklassen, Annahmen über die Reihenfolge der Methodenaufrufe und dergleichen zu machen - Solche Annahmen neigen nicht dazu   sei stabil, wenn sich die Oberklasse im Laufe der Zeit entwickeln könnte. Siehe auch mein Toaster   und Kaffeekanne Analogie.
  • Klassen werden schwerer - Sie wissen nicht unbedingt, was Ihre Superklasse in ihrem Konstruktor macht oder wie viel Speicher sie benötigt   benutzen. So kann man ein unschuldiges Möchtegern-Leichtbauobjekt bauen   viel teurer als Sie denken, und das kann sich im Laufe der Zeit ändern, wenn   Die Superklasse entwickelt sich
  • Es fördert eine Explosion von Unterklassen. Classloading kostet Zeit, mehr Klassen kosten Speicher. Dies kann ein Nicht-Problem sein, bis Sie es sind   Umgang mit einer App auf der Skala von NetBeans, aber da hatten wir echt   Probleme, bei denen zum Beispiel Menüs langsam sind, weil das erste Display angezeigt wird   eines Menüs löste massive Klassenladevorgänge aus. Wir haben das behoben, indem wir zu   mehr deklarative Syntax und andere Techniken, aber das kostet Zeit zu   auch reparieren.
  • Es macht es schwerer, Dinge später zu ändern - Wenn Sie eine Klasse public gemacht haben, wird der Austausch der Subklasse die Subklassen   Es ist eine Entscheidung, die du hast, sobald du den Code öffentlich gemacht hast   zu. Also, wenn Sie nicht die reale Funktionalität zu Ihrem ändern   Superklasse, Sie bekommen viel mehr Freiheit, um Dinge später zu ändern, wenn Sie   Verwenden Sie, anstatt das Ding zu verlängern, das Sie brauchen. Nehmen wir zum Beispiel   Untergliederung von JPanel - das ist normalerweise falsch; und wenn die Unterklasse ist   irgendwo öffentlich, bekommt man nie die Chance, diese Entscheidung zu überdenken. Ob   es wird als JComponent getThePanel () aufgerufen, Sie können es immer noch tun (Hinweis:   exponieren Sie Modelle für die Komponenten innerhalb Ihrer API).
  • Objekthierarchien werden nicht skaliert (oder sie später skalieren zu lassen, ist viel schwieriger als die Vorausplanung) - das ist der Klassiker "zu viele Schichten"   Problem. Ich werde im Folgenden darauf eingehen und wie das AskTheOracle-Muster aussehen kann   löse es (obwohl es OOP-Puristen beleidigen könnte).

...

Meine Entscheidung, was zu tun ist, wenn Sie eine Erbschaft zulassen, was Sie auch tun können   Nehmen Sie mit einem Körnchen Salz ist:

  • Expose keine Felder, immer, außer Konstanten
  • Die Methoden müssen entweder abstrakt oder endgültig sein
  • Rufen Sie keine Methoden aus dem Superklassenkonstruktor auf

...

All dies gilt weniger für kleine als für große Projekte und weniger   zu privaten Klassen als öffentliche


19
2018-05-21 01:59