it-swarm.com.de

Vererbung vs. Aggregation

Es gibt zwei Denkansätze, wie Code in einem objektorientierten System am besten erweitert, verbessert und wiederverwendet werden kann:

  1. Vererbung: Erweitern Sie die Funktionalität einer Klasse, indem Sie eine Unterklasse erstellen. Überschreiben Sie Oberklassenmitglieder in den Unterklassen, um neue Funktionen bereitzustellen. Machen Sie Methoden abstrakt/virtuell, um Unterklassen zum "Ausfüllen der Lücken" zu zwingen, wenn die Superklasse eine bestimmte Schnittstelle haben möchte, sich aber nicht mit ihrer Implementierung befasst.

  2. Aggregation: Erstellen Sie neue Funktionen, indem Sie andere Klassen zu einer neuen Klasse zusammenfassen. Fügen Sie dieser neuen Klasse eine gemeinsame Schnittstelle hinzu, um die Interoperabilität mit anderem Code zu gewährleisten.

Was sind die Vorteile, Kosten und Konsequenzen von jedem? Gibt es noch andere Alternativen?

Ich sehe, dass diese Debatte regelmäßig stattfindet, aber ich glaube, sie wurde noch nicht bei Stack Overflow abgefragt (obwohl es einige verwandte Diskussionen gibt). Es gibt auch einen überraschenden Mangel an guten Google-Ergebnissen dafür.

138
Craig Walker

Es geht nicht darum, welches das Beste ist, sondern darum, wann was zu verwenden ist.

In den "normalen" Fällen genügt eine einfache Frage, um herauszufinden, ob wir eine Vererbung oder Aggregation benötigen.

  • If Die neue Klasse entspricht mehr oder weniger der ursprünglichen Klasse. Verwenden Sie die Vererbung. Die neue Klasse ist jetzt eine Unterklasse der ursprünglichen Klasse.
  • Wenn die neue Klasse die ursprüngliche Klasse haben muss . Verwenden Sie die Aggregation. Die neue Klasse hat jetzt die ursprüngliche Klasse als Mitglied.

Es gibt jedoch eine große Grauzone. Wir brauchen also einige andere Tricks.

  • Wenn wir die Vererbung verwendet haben (oder planen, sie zu verwenden), aber nur einen Teil der Schnittstelle verwenden, oder wenn wir gezwungen sind, viele Funktionen zu überschreiben, um die Korrelation logisch zu halten. Dann haben wir einen großen üblen Geruch, der darauf hinweist, dass wir Aggregation verwenden mussten.
  • Wenn wir die Aggregation verwendet haben (oder planen, sie zu verwenden), stellen wir jedoch fest, dass wir fast die gesamte Funktionalität kopieren müssen. Dann haben wir einen Geruch, der in die Richtung der Vererbung weist.

Um es kurz zu machen. Wir sollten die Aggregation verwenden, wenn ein Teil der Schnittstelle nicht verwendet wird oder geändert werden muss, um eine unlogische Situation zu vermeiden. Wir müssen nur Vererbung verwenden, wenn wir fast die gesamte Funktionalität ohne größere Änderungen benötigen. Verwenden Sie im Zweifelsfall die Aggregation.

Eine andere Möglichkeit für den Fall, dass wir eine Klasse haben, die einen Teil der Funktionalität der ursprünglichen Klasse benötigt, besteht darin, die ursprüngliche Klasse in eine Stammklasse und eine Unterklasse zu teilen. Lassen Sie die neue Klasse von der Stammklasse erben. Aber Sie sollten darauf achten, keine unlogische Trennung zu schaffen.

Fügen wir ein Beispiel hinzu. Wir haben eine Klasse "Hund" mit Methoden: "Essen", "Gehen", "Bellen", "Spielen".

class Dog
  Eat;
  Walk;
  Bark;
  Play;
end;

Wir brauchen jetzt eine Klasse 'Cat', die 'Eat', 'Walk', 'Purr' und 'Play' braucht. Also zuerst versuchen, es von einem Hund zu verlängern.

class Cat is Dog
  Purr; 
end;

Sieht gut aus, aber warte. Diese Katze kann bellen (Katzenliebhaber werden mich dafür töten). Und eine bellende Katze verletzt die Prinzipien des Universums. Wir müssen also die Bark-Methode außer Kraft setzen, damit sie nichts bewirkt.

class Cat is Dog
  Purr; 
  Bark = null;
end;

Ok, das funktioniert, aber es riecht schlecht. Versuchen wir also eine Aggregation:

class Cat
  has Dog;
  Eat = Dog.Eat;
  Walk = Dog.Walk;
  Play = Dog.Play;
  Purr;
end;

Ok, das ist schön. Diese Katze bellt nicht mehr, auch nicht still. Aber es hat immer noch einen inneren Hund, der raus will. Versuchen wir also die dritte Lösung:

class Pet
  Eat;
  Walk;
  Play;
end;

class Dog is Pet
  Bark;
end;

class Cat is Pet
  Purr;
end;

Das ist viel sauberer. Keine internen Hunde. Und Katzen und Hunde sind auf dem gleichen Niveau. Wir können sogar andere Haustiere vorstellen, um das Modell zu erweitern. Es sei denn, es ist ein Fisch oder etwas, das nicht läuft. In diesem Fall müssen wir erneut umgestalten. Aber das ist etwas für eine andere Zeit.

169
Toon Krijthe

Zu Beginn von GOF geben sie an

Bevorzugen Sie die Objektzusammensetzung gegenüber der Klassenvererbung.

Dies wird weiter diskutiert hier

36
toolkit

Der Unterschied wird typischerweise als der Unterschied zwischen "ist a" und "hat a" ausgedrückt. Vererbung, das "ist eine" Beziehung, ist im Liskov-Substitutionsprinzip gut zusammengefasst. Aggregation, die "hat eine" Beziehung, ist genau das - es zeigt, dass das aggregierende Objekt eines der aggregierten Objekte hat .

Es gibt auch weitere Unterschiede - die private Vererbung in C++ zeigt an, dass eine Beziehung "implementiert in Bezug auf" vorliegt, die auch durch die Aggregation von (nicht offengelegten) Mitgliedsobjekten modelliert werden kann.

25
Harper Shelby

Hier ist mein häufigstes Argument:

In jedem objektorientierten System gibt es zwei Teile für jede Klasse:

  1. Sein Schnittstelle: das "öffentliche Gesicht" des Objekts. Dies ist der Satz von Fähigkeiten, den es dem Rest der Welt ankündigt. In vielen Sprachen ist die Menge gut als "Klasse" definiert. In der Regel handelt es sich hierbei um die Methodensignaturen des Objekts, die jedoch von Sprache zu Sprache leicht variieren.

  2. Seine Implementierung: die "hinter den Kulissen" Arbeit, die das Objekt tut, um seine Schnittstelle zu befriedigen und Funktionalität bereitzustellen. Dies sind normalerweise der Code und die Mitgliedsdaten des Objekts.

Eines der Grundprinzipien von OOP) ist, dass die Implementierung gekapselt (dh: versteckt) innerhalb der Klasse ist, das einzige, was Außenstehende sehen sollten, ist die Schnittstelle.

Wenn eine Unterklasse von einer Unterklasse erbt, erbt sie normalerweise beide die Implementierung und die Schnittstelle. Dies bedeutet wiederum, dass Sie gezwungen sind, beide als Einschränkungen für Ihre Klasse zu akzeptieren.

Bei der Aggregation können Sie entweder die Implementierung oder die Schnittstelle oder beides auswählen - aber Sie werden nicht dazu gezwungen. Die Funktionalität eines Objekts bleibt dem Objekt selbst überlassen. Es kann sich auf andere Objekte verschieben, wie es möchte, aber es ist letztendlich für sich selbst verantwortlich. Nach meiner Erfahrung führt dies zu einem flexibleren System, das einfacher zu ändern ist.

Wenn ich also objektorientierte Software entwickle, bevorzuge ich fast immer die Aggregation gegenüber der Vererbung.

14
Craig Walker

Ich gab eine Antwort auf "Ist ein" vs "Hat ein": welches ist besser? .

Grundsätzlich stimme ich anderen Leuten zu: Verwenden Sie die Vererbung nur, wenn Ihre abgeleitete Klasse wirklich ist den Typ, den Sie erweitern, nicht nur, weil sie enthält dieselben Daten enthält. Denken Sie daran, dass Vererbung bedeutet, dass die Unterklasse sowohl die Methoden als auch die Daten erhält.

Ist es für Ihre abgeleitete Klasse sinnvoll, alle Methoden der Oberklasse zu haben? Oder versprechen Sie sich nur leise, dass diese Methoden in der abgeleiteten Klasse ignoriert werden sollten? Oder überschreiben Sie Methoden aus der Oberklasse, indem Sie sie zu No-Ops machen, damit niemand sie versehentlich aufruft? Oder geben Sie Ihrem API-Dokumenterstellungstool Hinweise, um die Methode aus dem Dokument auszulassen?

Dies sind starke Anhaltspunkte dafür, dass in diesem Fall die Aggregation die bessere Wahl ist.

11
Bill Karwin

Ich sehe viele "is-a vs. has-a; sie sind konzeptionell unterschiedlich" Antworten auf diese und die damit verbundenen Fragen.

Die eine Sache, die ich in meiner Erfahrung gefunden habe, ist, dass der Versuch, festzustellen, ob eine Beziehung "ist-ein" oder "hat-ein" ist, zum Scheitern verurteilt ist. Auch wenn Sie diese Bestimmung jetzt korrekt für die Objekte vornehmen können, können sich die Anforderungen ändern und Sie werden wahrscheinlich irgendwann in der Zukunft falsch liegen.

Eine andere Sache, die ich gefunden habe, ist, dass es sehr schwer ist, von Vererbung zu Aggregation zu konvertieren, wenn eine Vererbungshierarchie viel Code enthält. Der Wechsel von einer Superklasse zu einer Schnittstelle bedeutet, dass nahezu jede Unterklasse im System geändert wird.

Und wie ich an anderer Stelle in diesem Beitrag erwähnt habe, ist die Aggregation in der Regel weniger flexibel als die Vererbung.

Sie haben also immer die perfekte Argumentation gegen die Vererbung, wenn Sie sich für das eine oder andere entscheiden müssen:

  1. Ihre Wahl wird wahrscheinlich irgendwann die falsche sein
  2. Diese Wahl zu ändern ist schwierig, wenn Sie sie einmal getroffen haben.
  3. Vererbung ist in der Regel eine schlechtere Wahl, da sie einschränkender ist.

Daher tendiere ich dazu, die Aggregation zu wählen - auch wenn es eine starke Beziehung zu geben scheint.

6
Craig Walker

Ich wollte dies als Kommentar zur ursprünglichen Frage machen, aber 300 Zeichen beißen [; <).

Ich denke, wir müssen vorsichtig sein. Erstens gibt es mehr Geschmacksrichtungen als die beiden in der Frage angeführten eher spezifischen Beispiele.

Ich empfehle auch, das Objektiv nicht mit dem Instrument zu verwechseln. Man möchte sicherstellen, dass die gewählte Technik oder Methodik das Erreichen des primären Ziels unterstützt, aber ich halte es nicht für unzusammenhängend, welche Technik die beste Diskussion ist. Es hilft, die Fallstricke der verschiedenen Ansätze zusammen mit ihren klaren Sweet Spots zu kennen.

Was möchten Sie beispielsweise erreichen, womit müssen Sie beginnen, und welche Einschränkungen bestehen?

Erstellen Sie ein Komponenten-Framework, auch ein spezielles? Sind Schnittstellen von Implementierungen im Programmiersystem trennbar oder wird dies durch eine Praxis mit einer anderen Art von Technologie erreicht? Können Sie die Vererbungsstruktur von Schnittstellen (falls vorhanden) von der Vererbungsstruktur von Klassen, die sie implementieren, trennen? Ist es wichtig, die Klassenstruktur einer Implementierung vor dem Code zu verbergen, der auf den von der Implementierung bereitgestellten Schnittstellen beruht? Sollen mehrere Implementierungen gleichzeitig verwendbar sein oder ist die Variation aufgrund von Wartung und Verbesserung im Laufe der Zeit größer? Dies und mehr müssen Sie berücksichtigen, bevor Sie sich auf ein Tool oder eine Methodik festlegen.

Schließlich ist es so wichtig, Unterscheidungen in der Abstraktion und wie Sie sie sehen (wie in is-a versus has-a), auf verschiedene Merkmale der OO) -Technologie zu beschränken? Vielleicht ja, wenn es hält die konzeptionelle Struktur für Sie und andere konsistent und überschaubar, aber es ist ratsam, sich davon und den möglichen daraus resultierenden Verzerrungen nicht unterkriegen zu lassen Erzählung, damit andere erkennen können, was los ist). [Ich suche nach Erklärungen für einen bestimmten Teil eines Programms, aber manchmal setze ich auf Eleganz, wenn es einen größeren Gewinn gibt.

Ich bin ein Schnittstellenpurist, und ich bin von den Problemen und Ansätzen angetan, bei denen Schnittstellenpurismus angebracht ist, sei es beim Erstellen eines Java= Frameworks oder beim Organisieren einiger COM-Implementierungen. Das macht es nicht Für alles geeignet, nicht einmal für alles in der Nähe, auch wenn ich es schwöre. (Ich habe ein paar Projekte, die ernsthafte Gegenbeispiele gegen den Grenzflächenpurismus liefern. Es wird also interessant sein zu sehen, wie ich damit zurechtkomme.)

3
orcmid

Die Frage lautet normalerweise Komposition vs. Vererbung und wurde hier schon einmal gestellt.

3
Bill the Lizard

Ich denke, es ist keine Entweder/Oder-Debatte. Es ist nur so dass:

  1. is-a-Beziehungen (Vererbungsbeziehungen) treten seltener auf als has-a-Beziehungen (Kompositionsbeziehungen).
  2. Vererbung ist schwieriger zu korrigieren, selbst wenn es angemessen ist, sie zu verwenden. Daher muss gebührende Sorgfalt aufgewendet werden, da sie die Kapselung aufbrechen und eine enge Kopplung fördern kann, indem die Implementierung offengelegt wird und so weiter.

Beide haben ihren Platz, aber die Vererbung ist riskanter.

Obwohl es natürlich keinen Sinn macht, Klassen Shape mit einem Punkt und einem Quadrat zu haben. Hier ist die Vererbung fällig.

Die Leute neigen dazu, sich zuerst Gedanken über Vererbung zu machen, wenn sie versuchen, etwas Erweiterbares zu entwerfen. Genau das ist falsch.

2
Vinko Vrsalovic

Gunst geschieht, wenn sich beide Kandidaten qualifizieren. A und B sind Optionen und Sie bevorzugen A. Der Grund dafür ist, dass Komposition mehr Erweiterungs-/Flexibilitätsmöglichkeiten bietet als Generalisierung. Diese Erweiterung/Flexibilität bezieht sich hauptsächlich auf die Laufzeit/dynamische Flexibilität.

Der Nutzen ist nicht sofort sichtbar. Um den Nutzen zu sehen, müssen Sie auf die nächste unerwartete Änderungsanforderung warten. In den meisten Fällen scheitern diejenigen, die an der Generierung festhalten, im Vergleich zu denen, die die Komposition befürworten (mit Ausnahme eines offensichtlichen Falles, der später erwähnt wird). Daher die Regel. Wenn Sie eine Abhängigkeitsinjektion erfolgreich implementieren können, sollten Sie aus Lernsicht wissen, welche zu bevorzugen ist und wann. Die Regel hilft Ihnen auch bei der Entscheidungsfindung. Wenn Sie sich nicht sicher sind, wählen Sie die Komposition.

Zusammenfassung: Zusammensetzung: Die Kopplung wird reduziert, indem Sie nur einige kleinere Dinge in etwas Größeres stecken, und das größere Objekt ruft das kleinere Objekt zurück. Generierung: Aus API-Sicht ist die Definition, dass eine Methode überschrieben werden kann, eine stärkere Verpflichtung als die Definition, dass eine Methode aufgerufen werden kann. (sehr wenige Gelegenheiten, wenn die Generalisierung gewinnt). Und vergessen Sie niemals, dass Sie bei der Komposition auch die Vererbung von einer Schnittstelle anstelle einer großen Klasse verwenden

1
Blue Clouds

Ich werde den Teil besprechen, wo diese möglicherweise angewendet werden. Hier ist ein Beispiel für beides in einem Spielszenario. Angenommen, es gibt ein Spiel, in dem es verschiedene Arten von Soldaten gibt. Jeder Soldat kann einen Rucksack haben, der verschiedene Dinge aufnehmen kann.

Vererbung hier? Es gibt eine Marine, eine grüne Baskenmütze und einen Scharfschützen. Dies sind Arten von Soldaten. Es gibt also eine Basisklasse Soldier mit Marine, Green Beret & Sniper als abgeleiteten Klassen

Aggregation hier? Der Rucksack kann Granaten, Gewehre (verschiedene Typen), Messer, Medikit usw. enthalten. Ein Soldat kann zu jedem beliebigen Zeitpunkt mit diesen ausgerüstet sein, und er kann auch eine haben kugelsichere Weste, die bei Angriffen als Rüstung fungiert und deren Verletzung auf einen bestimmten Prozentsatz sinkt. Die Soldatenklasse enthält ein Objekt der kugelsicheren Westenklasse und die Rucksackklasse, die Verweise auf diese Gegenstände enthält.

1
Salman Kasbati

Beide Ansätze dienen zur Lösung unterschiedlicher Probleme. Sie müssen nicht immer zwei oder mehr Klassen zusammenfassen, wenn Sie von einer Klasse erben.

Manchmal müssen Sie eine einzelne Klasse aggregieren, weil diese Klasse versiegelt ist oder auf andere Weise nicht virtuelle Mitglieder hat, die Sie abfangen müssen, damit Sie eine Proxy-Ebene erstellen, die offensichtlich nicht in Bezug auf die Vererbung gültig ist, sondern solange die Klasse, die Sie vertreten hat eine Schnittstelle, die Sie abonnieren können, kann ziemlich gut funktionieren.

0
cfeduke