1984 Stimmen

Bevorzugen Sie Komposition statt Vererbung?

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

6 Stimmen

6 Stimmen

In einem Satz Vererbung ist öffentlich, wenn Sie eine öffentliche Methode haben und Sie ändern es ändert die veröffentlichte api. wenn Sie Zusammensetzung haben und das Objekt komponiert hat sich geändert, müssen Sie nicht Ihre veröffentlichte api ändern.

1446voto

Gishu Punkte 130442

Ziehen Sie die Komposition der Vererbung vor, da sie flexibler ist und später leicht geändert werden kann, aber verwenden Sie nicht den Ansatz "Komposition immer". Bei der Komposition ist es einfach, das Verhalten mit Dependency Injection / Setters zu ändern. Die Vererbung ist starrer, da die meisten Sprachen nicht zulassen, dass man von mehr als einem Typ ableitet. Die Gans ist also mehr oder weniger gegessen, sobald man von TypA ableitet.

Meine Nagelprobe für die obigen Ausführungen ist:

  • Möchte TypB die vollständige Schnittstelle (alle öffentlichen Methoden) von TypA offenlegen, so dass TypB dort verwendet werden kann, wo TypA erwartet wird? Zeigt an Vererbung .

    • Ein Cessna-Doppeldecker wird z. B. die gesamte Schnittstelle eines Flugzeugs aufweisen, wenn nicht mehr. Das macht es also geeignet, von Airplane abzuleiten.
  • Möchte TypB nur einen Teil des Verhaltens von TypA? Zeigt den Bedarf an Zusammensetzung.

    • Ein Vogel braucht z.B. 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.

Aktualisierung: Ich bin gerade noch einmal auf meine Antwort zurückgekommen, und es scheint, dass sie unvollständig ist, wenn man nicht ausdrücklich auf Barbara Liskovs Liskov-Substitutionsprinzip als Test für "Soll ich von diesem Typ erben?

111 Stimmen

Das zweite Beispiel stammt direkt aus den Head First Design Patterns ( amazon.com/First-Design-Patterns-Elisabeth-Freeman/dp/ ) Buch :) Ich würde dieses Buch jedem empfehlen, der diese Frage gegoogelt hat.

4 Stimmen

Das ist sehr klar, aber es könnte etwas fehlen: "Will TypB die vollständige Schnittstelle (alle öffentlichen Methoden) von TypA offenlegen, so dass TypB dort verwendet werden kann, wo TypA erwartet wird?" Aber was ist, wenn dies wahr ist und TypB auch die vollständige Schnittstelle von TypC offenlegt? Und was ist, wenn TypC noch nicht modelliert wurde?

8 Stimmen

Sie spielen auf das an, was meiner Meinung nach der grundlegendste Test sein sollte: "Sollte dieses Objekt von einem Code verwendet werden können, der Objekte des Basistyps erwartet". Wenn die Antwort ja lautet, sollte das Objekt debe vererben. Wenn nein, dann sollte es wahrscheinlich nicht sein. Wenn es nach mir ginge, würden Sprachen ein Schlüsselwort zur Verfügung stellen, das auf "diese Klasse" verweist, und eine Möglichkeit bieten, eine Klasse zu definieren, die sich wie eine andere Klasse verhält, aber nicht durch sie ersetzbar ist (bei einer solchen Klasse würden alle "diese Klasse"-Verweise durch sie selbst ersetzt).

561voto

Nick Zalutskiy Punkte 14210

Betrachten Sie die Eindämmung als eine hat eine Beziehung. Ein Auto "hat einen" Motor, eine Person "hat einen" Namen, usw.

Betrachten Sie die Vererbung als eine ist eine Beziehung. Ein Auto "ist" ein Fahrzeug, ein Mensch "ist" ein Säugetier, usw.

Dieser Ansatz ist nicht mein Verdienst. Ich habe ihn direkt aus dem Zweite Ausgabe von Code Complete por Steve McConnell , Abschnitt 6.3 .

135 Stimmen

Dies ist nicht immer ein perfekter Ansatz, sondern nur ein guter Leitfaden. Das Liskov-Substitutionsprinzip ist viel genauer (scheitert weniger).

2 Stimmen

Können Sie nicht die meisten ist eine a hat eine Beziehungen? So hat ein Auto ein basicVehicle-Objekt und eine Person ein basicMammal-Objekt, usw.

51 Stimmen

"Mein Auto hat ein Fahrzeug." Wenn man das als separaten Satz betrachtet, nicht in einem Programmierkontext, ergibt das absolut keinen Sinn. Und genau das ist der Sinn dieser Technik. Wenn es unangenehm klingt, ist es wahrscheinlich falsch.

280voto

aleemb Punkte 29695

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

Verfahrenskodex

Ein Beispiel dafür ist PHP ohne die Verwendung von Klassen (insbesondere vor PHP5). Die gesamte Logik ist in einer Reihe von Funktionen kodiert. Sie können andere Dateien mit Hilfsfunktionen usw. einbinden und Ihre Geschäftslogik ausführen, indem Sie Daten in Funktionen weitergeben. Das kann sehr schwer zu handhaben sein, wenn die Anwendung wächst. PHP5 versucht hier Abhilfe zu schaffen, indem es mehr objektorientiertes Design bietet.

Vererbung

Dadurch wird die Nutzung von Klassen gefördert. Die Vererbung ist einer der drei Grundsätze des OO-Designs (Vererbung, Polymorphie, Kapselung).

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

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

Das ist Vererbung bei der Arbeit. Der Mitarbeiter "ist eine" Person oder erbt von einer Person. Alle Vererbungsbeziehungen sind "is-a"-Beziehungen. Employee erbt auch die Eigenschaft Title von Person, d. h. Employee.Title gibt den Titel für den Mitarbeiter und nicht für die Person zurück.

Zusammensetzung

Die Komposition wird der Vererbung vorgezogen. Um es ganz einfach auszudrücken, würden Sie haben:

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 Komposition ist in der Regel eine "hat eine" oder "verwendet eine" Beziehung. Hier hat die Klasse Employee eine Person. Sie erbt nicht von Person, sondern bekommt stattdessen das Person-Objekt übergeben, weshalb sie "has a" Person ist.

Komposition über Vererbung

Angenommen, Sie möchten einen Managertyp erstellen, so dass Sie am Ende Folgendes erhalten:

class Manager : Person, Employee {
   ...
}

Dieses Beispiel wird gut funktionieren, aber was wäre, wenn Person und Employee beide als Title ? Sollte Manager.Title "Manager of Operations" oder "Mr." zurückgeben? Unter Komposition ist diese Mehrdeutigkeit besser aufgehoben:

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

Das Objekt Manager besteht aus einem Mitarbeiter und einer Person. Das Verhalten "Titel" wird von "Mitarbeiter" übernommen. Durch diese explizite Zusammensetzung wird unter anderem die Mehrdeutigkeit beseitigt, und Sie werden weniger Bugs finden.

8 Stimmen

Zum Vererben: Es gibt keine Zweideutigkeit. Sie implementieren die Klasse Manager auf der Grundlage von Anforderungen. Sie würden also "Manager of Operations" zurückgeben, wenn dies in Ihren Anforderungen festgelegt ist, andernfalls würden Sie einfach die Implementierung der Basisklasse verwenden. Sie könnten Person auch zu einer abstrakten Klasse machen und damit sicherstellen, dass nachgelagerte Klassen eine Title-Eigenschaft implementieren.

96 Stimmen

Es ist wichtig, sich daran zu erinnern, dass man "Zusammensetzung über Vererbung" sagen kann, aber das bedeutet nicht "Zusammensetzung immer über Vererbung". "Is a" bedeutet Vererbung und führt zur Wiederverwendung von Code. Mitarbeiter ist eine Person (Mitarbeiter hat keine Person).

2 Stimmen

Sie müssten zum Zeitpunkt der Erstellung der ursprünglichen Personenklasse wissen, dass Sie die Bedeutung des Titels ändern können möchten, abstrakte Eigenschaften sind ein Zeichen für schlechtes Design, Verhalten sollte abstrakt sein, nicht der Zustand.

215voto

Ahmed Punkte 10357

Bei all den unbestreitbaren Vorteilen, die eine Erbschaft mit sich bringt, sind hier auch einige Nachteile aufgeführt.

Nachteile der Vererbung:

  1. Sie können die von Superklassen geerbte Implementierung zur Laufzeit nicht ändern (da die Vererbung offensichtlich zur Kompilierzeit definiert wird).
  2. Vererbung setzt eine Unterklasse den Details der Implementierung ihrer Elternklasse aus. Deshalb wird oft gesagt, dass Vererbung die Kapselung bricht (in dem Sinne, dass man sich wirklich nur auf die Schnittstellen und nicht auf die Implementierung konzentrieren muss, so dass die Wiederverwendung durch Unterklassen nicht immer vorzuziehen ist).
  3. Durch die enge Kopplung, die durch die Vererbung entsteht, ist die Implementierung einer Unterklasse sehr stark an die Implementierung einer Oberklasse gebunden, so dass jede Änderung in der übergeordneten Implementierung die Unterklasse zur Änderung zwingt.
  4. Eine übermäßige Wiederverwendung durch Unterklassenbildung kann den Vererbungsstapel sehr tief und auch sehr unübersichtlich machen.

Andererseits Zusammensetzung der Objekte wird zur Laufzeit durch Objekte definiert, die Referenzen auf andere Objekte erwerben. In einem solchen Fall können diese Objekte niemals auf die geschützten Daten des jeweils anderen zugreifen (keine Unterbrechung der Kapselung) und sind gezwungen, die Schnittstelle des jeweils anderen zu respektieren. Und auch in diesem Fall sind die Implementierungsabhängigkeiten viel geringer als bei der Vererbung.

13 Stimmen

Dies ist meiner Meinung nach eine der besseren Antworten. Ich möchte noch hinzufügen, dass der Versuch, Ihre Probleme im Hinblick auf die Komposition zu überdenken, meiner Erfahrung nach zu kleineren, einfacheren, in sich geschlossenen und wiederverwendbaren Klassen führt, mit einem klareren, kleineren und gezielteren Verantwortungsbereich. Oft bedeutet dies, dass weniger Bedarf an Dingen wie Dependency Injection oder Mocking (in Tests) besteht, da kleinere Komponenten normalerweise in der Lage sind, für sich selbst zu stehen. Nur meine Erfahrung. YMMV :-)

4 Stimmen

Der letzte Absatz in diesem Beitrag hat bei mir wirklich Klick gemacht. Ich danke Ihnen.

1 Stimmen

Auch wenn Sie eine gute Technik erwähnen, glaube ich nicht, dass Sie die Frage beantworten. Es geht nicht um den Vergleich von Laufzeit und Kompilierzeit, sondern um Vererbung und Komposition, die Code aus EINER oder VIELEN Klassen WIEDERVERWENDET und neue Logik ÜBERSTEHEN oder ZUSÄTZEN kann. Was Sie als Objektkomposition beschreiben, ist einfach das Einfügen von Logik über Objekte, die als Eigenschaften in einer Klasse zugewiesen sind, was eine großartige Alternative für die Manipulation zur Laufzeit ist. Vielen Dank!

112voto

Tim Howland Punkte 7781

Ein weiterer, sehr pragmatischer Grund, Komposition der Vererbung vorzuziehen, hat mit Ihrem Domänenmodell und dessen Abbildung auf eine relationale Datenbank zu tun. Es ist wirklich schwierig, die Vererbung auf das SQL-Modell abzubilden (man endet mit allen möglichen hacky Workarounds, wie dem Erstellen von Spalten, die nicht immer verwendet werden, der Verwendung von Views usw.). Einige ORMLs versuchen, dieses Problem zu lösen, aber es wird immer schnell kompliziert. Komposition kann leicht durch eine Fremdschlüsselbeziehung zwischen zwei Tabellen modelliert werden, aber Vererbung ist viel schwieriger.

CodeJaeger.com

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

Powered by:

X