Zusammensetzung über Vererbung aber

13

Ich versuche mir Software-Engineering beizubringen und stoße auf widersprüchliche Informationen, die mich verwirren.

Ich habe OOP gelernt und was abstrakte Klassen / Schnittstellen sind und wie man sie verwendet, aber dann lese ich, dass man "Komposition gegenüber Vererbung bevorzugen" sollte. Ich verstehe Komposition ist, wenn eine Klasse ein Objekt einer anderen Klasse erstellt / erstellt, um die Funktionalität dieses neuen Objekts zu nutzen / mit ihm zu interagieren.

Meine Frage ist also ... Soll ich keine abstrakten Klassen und Schnittstellen verwenden? Um keine abstrakte Klasse zu erstellen und die Funktionalität dieser abstrakten Klasse in den konkreten Klassen zu erweitern / zu erben, und stattdessen einfach neue Objekte zu erstellen, um die Funktionalität der anderen Klasse zu nutzen?

Oder soll ich Komposition verwenden UND von abstrakten Klassen erben? beide zusammen verwenden? Wenn ja, könnten Sie einige Beispiele dafür nennen, wie dies funktionieren würde und welche Vorteile es hat?

Da ich mit PHP am besten vertraut bin, verwende ich es, um meine OOP / SE-Kenntnisse zu verbessern, bevor ich zu anderen Sprachen übergehe und meine neu erworbenen SE-Kenntnisse übertrage. Beispiele für die Verwendung von PHP wären daher sehr willkommen.

MikeMason
quelle
2
"Komposition gegenüber Vererbung bevorzugen" ist eine dumme Idee, die genauso sinnvoll ist wie "Sägen gegenüber Bohrern bevorzugen". Es handelt sich um zwei verschiedene Tools, die für zwei verschiedene Anwendungsfälle geeignet sind, und die Zeiten, in denen Sie eines der beiden Tools wirklich austauschbar verwenden können, sind eigentlich ziemlich selten.
Mason Wheeler
2
"Bevorzugen Sie X gegenüber Y" bedeutet nicht, niemals Y zu verwenden. Denken Sie nur darüber nach, ob X besser wäre
Richard Tingle
2
"Komposition gegenüber Vererbung bevorzugen" wird genauer ausgedrückt als "Verwenden Sie niemals Klassenvererbung", mit der Klarstellung, dass das Implementieren einer Schnittstelle keine Vererbung ist und wenn Ihre gewählte Sprache nur abstrakte Klassen unterstützt, keine Schnittstellen, die von 100% erben Die abstrakte Klasse kann auch als "keine Vererbung" angesehen werden.
David Arno
1
@MasonWheeler, es gibt keine gültigen Anwendungsfälle für die Vererbung. Es ist mindestens so "böse" wie "goto".
David Arno
1
@ DavidArno Das ist einfach nur lächerlich. Vererbung ist eine der nützlichsten und produktivsten Funktionen, die jemals in der gesamten Geschichte der Programmierung entwickelt wurden. Wie bei allem Nützlichen gibt es viele Möglichkeiten, es zu missbrauchen, aber bei richtiger Anwendung erhöht es die Leistung und Produktivität Ihrer Arbeit massiv.
Mason Wheeler

Antworten:

19

Komposition über Vererbung bedeutet, dass es häufig besser ist, eine andere Klasse zu erstellen, die die vorhandene Klasse umschließt und ihre Implementierung intern verwendet, wenn Sie die Funktionalität einer vorhandenen Klasse wiederverwenden oder erweitern möchten. Ein Beispiel hierfür ist das Dekorationsmuster .

Vererbung ist keine gute Standardmethode für den Umgang mit Wiederverwendungsszenarien, da Sie häufig nur einen Teil der Funktionalität einer Basisklasse verwenden möchten und die Unterklasse nicht den gesamten Vertrag der Basisklasse auf eine Weise unterstützen kann, die die Liskov-Substitution erfüllt Prinzip .

Die Vorlagenmethode ist ein gutes Beispiel für einen Fall, in dem eine Vererbung angemessen ist.

In dieser Antwort finden Sie Richtlinien zur Auswahl zwischen Komposition und Vererbung.

astreltsov
quelle
+1, um darauf hinzuweisen, dass bei teilweiser Wiederverwendung einer Klasse der nicht verwendete Teil bei der Komposition sauberer ignoriert wird als bei der Vererbung.
Lawrence
4

Ich bin mit PHP nicht vertraut genug, um Ihnen konkrete Beispiele in dieser Sprache zu geben, aber hier sind einige Richtlinien, die ich als nützlich empfunden habe.

Schnittstellen / Merkmale sind nützlich, um das Verhalten in vielen Klassen zu definieren, die möglicherweise nur sehr wenig gemeinsam haben.

Wenn Sie beispielsweise an einer JSON-API arbeiten, können Sie eine Schnittstelle definieren, die zwei Methoden angibt: "toJson" und "toStatusCode". Auf diese Weise können Sie einen Helfer schreiben, der jedes Objekt, das diese Schnittstelle implementiert, in eine HTTP-Antwort konvertieren kann.

In den meisten Fällen ist dies der direkten Vererbung vorzuziehen, da es weniger wahrscheinlich ist, dass das fragile Problem der Basisklasse auftritt, da es zu flachen Klassenhierarchien tendiert.

Direkte Vererbung wird am besten als etwas angesehen, das Sie tun, wenn es zwingende Gründe dafür gibt, und nicht als etwas, das Sie tun, es sei denn, es gibt zwingende Gründe, dies nicht zu tun.

In Sprachen, die keine Standardimplementierungen in Schnittstellen unterstützen, ist es möglicherweise eine vernünftige Möglichkeit, eine Reihe von Basisfunktionalitäten gemeinsam zu nutzen, schränkt jedoch in der Regel die Möglichkeiten der Unterklassen stark ein (Mehrfachvererbung, sofern verfügbar, ist den Aufwand selten wert). . Oft finde ich, dass es sich lohnt, die Boilerplate-Redirect-Methoden zu schreiben, um stattdessen Komposition zu verwenden.

Die Zusammensetzung sollte Ihre Standardstrategie sein. Verfassen Sie so viel wie möglich mit Schnittstellen und übergeben Sie sie als Konstruktorargumente. Dies erleichtert Ihnen das Schreiben von Tests erheblich.

Vermeiden Sie es, so oft wie möglich mit globalen Singletons zu arbeiten, da der Vergleich der erwarteten und tatsächlichen Ausgabe ein richtiger Schmerz ist, wenn ein Teil der Ausgabe vom Status der Systemuhr abhängt.

Dies ist natürlich nicht immer möglich. Das Play-Webframework bietet beispielsweise eine JSON-Bibliothek, die im Grunde eine domänenspezifische Sprache ist. Dies zu ändern wäre ein großes Unterfangen, von dem das Ersetzen der Aufrufe von 'Json.prettyPrint' nur ein sehr untergeordneter Teil wäre.

Morgen
quelle
1

Zusammensetzung ist, wenn eine Klasse einige Funktionen bietet, indem sie eine (möglicherweise interne) Klasse instanziiert, die diese Funktionalität bereits implementiert, anstatt von dieser Klasse zu erben.

Wenn Sie beispielsweise eine Klasse haben, die ein Schiff modelliert, und Ihnen jetzt gesagt wird, dass Ihr Schiff einen Hubschrauberlandeplatz anbieten soll, ist es nicht selbstverständlich, Ihr Schiff stattdessen von einem Hubschrauberlandeplatz abzuleiten (duh!) Ihr Schiff enthält eine Hubschrauberlandeplatzklasse und legt sie auf irgendeine Ship.getHelipad()Weise frei.

In früheren Jahren (vor etwa einem Jahrzehnt) betrachteten die Menschen die Vererbung als eine schnelle und einfache Möglichkeit, die Funktionalität zu aggregieren. Daher gab es viele Beispiele für die Art "Schiff, das vom Hubschrauberlandeplatz erbt", die natürlich sehr lahm waren.

Das Diktum "Bevorzugung der Komposition gegenüber Vererbung" wurde jedoch sorgfältig formuliert, um deutlich zu machen, dass dies nur ein Vorschlag und keine Regel ist. Der Verfasser des Diktums war vorsichtig genug, um nicht zu sagen: "Du sollst niemals Vererbung verwenden, nur Komposition". Dies macht die Softwareentwickler im Grunde auf die Tatsache aufmerksam, dass die Vererbung überbeansprucht wurde, während in vielen Fällen die Komposition klarere, elegantere und wartbarere Designs hervorbringt als die Vererbung.

Das Diktum "Bevorzugen Sie die Komposition gegenüber der Vererbung" schlägt also im Wesentlichen vor, wann immer Sie mit dem "Erben oder Komponieren" konfrontiert sind. Frage, Sie sollten sich überlegen, was die am besten geeignete Strategie ist und dass die meisten Chancen bestehen, dass sich die am besten geeignete Strategie als Zusammensetzung und nicht als Vererbung herausstellt.

Da dies jedoch keine Regel ist, sollten Sie auch berücksichtigen, dass es viele Fälle gibt, in denen die Vererbung natürlicher ist. Wenn Sie dort Komposition verwenden, wo Sie Vererbung hätten verwenden sollen, werden viele Übel Ihren Code treffen.

Um zum Schiffsbeispiel zurückzukehren: Wenn Ihr Schiff eine Schnittstelle für die Interaktion mit a bieten muss, FloatingMachineist es natürlicher, es von einer abstrakten FloatingMachineKlasse abzuleiten , die möglicherweise wiederum von einer anderen abstrakten MachineKlasse abgeleitet wird.

Hier ist die Faustregel für die Antwort auf die Frage Zusammensetzung / Vererbung:

Hat meine Klasse eine "Ist eine" Beziehung zu der Schnittstelle, die sie verfügbar machen muss? Wenn ja, verwenden Sie die Vererbung. Wenn nicht, verwenden Sie Komposition.

Ein Schiff ist eine schwimmende Maschine, und eine schwimmende Maschine ist eine Maschine. Vererbung ist also für diese vollkommen in Ordnung. Aber ein Schiff ist natürlich kein Hubschrauberlandeplatz. So kann die Funktionalität des Hubschrauberlandeplatzes besser zusammengestellt werden.

Mike Nakis
quelle