Können wir die Vererbung mithilfe von Strategiemustern und Abhängigkeitsinjektion vollständig ersetzen?


10

Zum Beispiel:

var duckBehaviors = new Duckbehavior();
duckBehaviors.quackBehavior = new Quack();
duckBehaviors.flyBehavior = new FlyWithWings();
Duck mallardDuck = new Duck(DuckTypes.MallardDuck, duckBehaviors)

Da die Duck-Klasse alle Verhaltensweisen enthält (abstrakt), scheint das Erstellen einer neuen Klasse MallardDuck(die erweitert wird Duck) nicht erforderlich zu sein.

Referenz: Head First Design Pattern, Kapitel 1.


Welche Arten Duckbehavior.quackBehaviorund andere Felder enthält Ihr Code?
max630

In Ihrem Beispiel gibt es keine Abhängigkeitsinjektion.
David Conrad

11
Vererbung ist fantastisch, wenn Sie ein Junior- bis Mid-Level-Entwickler sind, da es eine lange Geschichte von Refactoring-Tricks und Designmustern gibt. Aber die meisten erfahrenen Entwickler, mit denen ich gesprochen habe, bevorzugen nach Möglichkeit wirklich flache Vererbungshierarchien, die auf der Zusammensetzung basieren. Vererbung kann eine engere Kopplung als nötig verursachen und Änderungen erschweren.
Mark Rogers


5
@ DavidConrad: Das OP führt die Neuerungen innerhalb der Klasse nicht durch . Bei DI / IoC geht es darum, Abhängigkeiten einzufügen, nicht um Container oder darum, niemals "neu" zu verwenden. Das ist ein völlig orthogonales Problem. Außerdem müssen Sie den Code immer irgendwo ändern - sei es im Kompositionsstamm oder in einer Konfigurationsdatei. Das Steuerelement wird hier innerhalb des Duck-Typs invertiert , da das Element, das die Erstellung der Abhängigkeiten steuert, nicht der Duck-Ctor ist, sondern ein externer Kontext. Da dies aus Gründen der Klarheit ein Spielzeugbeispiel ist, ist es vollkommen in Ordnung, dass der äußere Kontext nur durch den aufrufenden Code dargestellt wird.
Filip Milovanović

Antworten:


21

Sicher, aber wir nennen das Zusammensetzung und Delegation . Das Strategiemuster und die Abhängigkeitsinjektion mögen strukturell ähnlich erscheinen, aber ihre Absichten sind unterschiedlich.

Das Strategiemuster ermöglicht eine Laufzeitänderung des Verhaltens unter derselben Schnittstelle. Ich konnte einer Stockente sagen, sie solle fliegen und sie mit Flügeln fliegen sehen. Tauschen Sie es dann gegen eine Jet-Pilot-Ente aus und beobachten Sie, wie es mit Delta Airlines fliegt. Dies zu tun, während das Programm ausgeführt wird, ist eine Strategie-Muster-Sache.

Die Abhängigkeitsinjektion ist eine Technik zur Vermeidung von Abhängigkeiten bei der harten Codierung, sodass sie unabhängig voneinander geändert werden können, ohne dass Clients bei Änderungen geändert werden müssen. Kunden drücken einfach ihre Bedürfnisse aus, ohne zu wissen, wie sie erfüllt werden. Daher wird an anderer Stelle (in der Regel hauptsächlich) entschieden, wie sie erfüllt werden. Sie brauchen keine zwei Enten, um diese Technik anzuwenden. Nur etwas, das eine Ente benutzt, ohne zu wissen oder sich darum zu kümmern, welche Ente. Etwas, das die Ente nicht baut oder danach sucht, aber vollkommen glücklich ist, die Ente zu verwenden, die Sie ihr geben.

Wenn ich eine konkrete Entenklasse habe, kann ich sie das Flugverhalten implementieren lassen. Ich könnte sogar das Verhalten von Fly-with-Wings auf Fly-with-Delta basierend auf einer Zustandsvariablen ändern lassen. Diese Variable kann ein Boolescher Wert, ein Int oder FlyBehavioreine flyMethode sein , die eine Methode hat, die jeden Flugstil ausführt, ohne dass ich sie mit einem if testen muss. Jetzt kann ich den Flugstil ändern, ohne den Ententyp zu ändern. Jetzt können Stockenten Piloten werden. Dies ist Zusammensetzung und Delegation . Die Ente besteht aus einem FlyBehavior und kann Fluganfragen an sie delegieren. Auf diese Weise können Sie alle Verhaltensweisen Ihrer Enten auf einmal ersetzen oder für jedes Verhalten oder eine beliebige Kombination dazwischen etwas halten.

Dies gibt Ihnen alle die gleichen Kräfte, die die Vererbung außer einer hat. Mit der Vererbung können Sie ausdrücken, welche Duck-Methoden Sie in den Duck-Subtypen überschreiben. Für die Komposition und Delegierung muss die Ente von Anfang an explizit an Subtypen delegieren. Dies ist weitaus flexibler, erfordert jedoch mehr Tastatureingaben und Duck muss wissen, dass dies geschieht.

Viele Menschen glauben jedoch, dass die Vererbung von Anfang an explizit ausgelegt werden muss. Und wenn dies nicht der Fall ist, sollten Sie Ihre Klassen als versiegelt / endgültig markieren, um die Vererbung zu verbieten. Wenn Sie diese Ansicht vertreten, hat die Vererbung keinen Vorteil gegenüber der Zusammensetzung und Delegierung. Denn dann muss man so oder so entweder von Anfang an auf Erweiterbarkeit ausgelegt sein oder bereit sein, Dinge später abzureißen.

Abreißen ist eigentlich eine beliebte Option. Seien Sie sich nur bewusst, dass es Fälle gibt, in denen es ein Problem gibt. Wenn Sie Bibliotheken oder Codemodule unabhängig voneinander bereitgestellt haben, die Sie mit der nächsten Version nicht aktualisieren möchten, kann es sein, dass Sie nicht mehr mit Versionen von Klassen zu tun haben, die nichts über Ihre aktuellen Aktivitäten wissen.

Die Bereitschaft, Dinge später abzureißen, kann Sie von übermäßigem Entwerfen befreien. Es ist jedoch sehr mächtig, etwas zu entwerfen, das eine Ente verwendet, ohne wissen zu müssen, was die Ente tatsächlich tun wird, wenn sie verwendet wird. Das Nichtwissen ist mächtiges Zeug. So können Sie für eine Weile aufhören, an Enten zu denken, und über den Rest Ihres Codes nachdenken.

"Können wir" und "sollten wir" sind unterschiedliche Fragen. Die Komposition gegenüber der Vererbung zu bevorzugen bedeutet nicht, niemals die Vererbung zu verwenden. Es gibt immer noch Fälle, in denen Vererbung am sinnvollsten ist. Ich zeige Ihnen mein Lieblingsbeispiel :

public class LoginFailure : System.ApplicationException {}

Mit der Vererbung können Sie Ausnahmen mit spezifischeren, beschreibenden Namen in nur einer Zeile erstellen.

Versuchen Sie das mit Komposition und Sie werden ein Chaos bekommen. Es besteht auch kein Risiko für das Vererbungs- Jojo-Problem, da hier keine Daten oder Methoden zur Wiederverwendung und Förderung der Vererbungsverkettung vorhanden sind. Das alles fügt einen guten Namen hinzu. Unterschätze niemals den Wert eines guten Namens.


1
" Unterschätze niemals den Wert eines guten Namens ". Kaisers neue Kleiderzeit: LoginExceptionist kein guter Name. Es ist ein klassisches "Schlumpf-Benennen" und wenn ich es wegnehme Exception, ist alles, was ich habe Login, es sagt mir nichts darüber, was schief gelaufen ist.
David Arno

Leider halten wir an der lächerlichen Konvention fest, Exceptionjede Ausnahmeklasse zu beenden, aber bitte verwechseln Sie "festgefahren" nicht mit "gut".
David Arno

@ DavidArno Wenn Sie einen besseren Namen vorschlagen können, bin ich ganz Ohr. Hier haben wir den Vorteil, dass wir nicht in den Konventionen einer vorhandenen Codebasis gefangen sind. Wenn Sie die Macht hätten, die Welt mit einem Fingerschnipp zu verändern, wie würden Sie es nennen?
candied_orange

Ich würde nennen sie, StackOverflow, OutOfMemory, NullReferenceAccess, LoginFailureusw. Grundsätzlich nehmen „Exception“ aus dem Namen. Und wenn nötig, beheben Sie sie so, dass beschrieben wird, was schief gelaufen ist.
David Arno

@ DavidArno auf Ihren Befehl.
candied_orange

7

Sie können fast jede Methodik durch eine andere Methodik ersetzen und trotzdem funktionierende Software erstellen. Einige passen jedoch besser zu einem bestimmten Problem als andere.

Es kommt auf viele Dinge an, die man vorziehen kann. Stand der Technik in der Anwendung, Erfahrung im Team, erwartete zukünftige Entwicklungen, persönliche Vorlieben und wie schwierig es für einen Neuling wahrscheinlich sein wird, sich damit auseinanderzusetzen, um nur einige zu nennen.

Wenn Sie erfahrener werden und häufiger mit den Kreationen anderer Menschen zu kämpfen haben, werden Sie wahrscheinlich mehr Wert auf die letzte Betrachtung legen.

Vererbung ist immer noch ein gültiges und starkes Modellierungswerkzeug, das nicht unbedingt immer das flexibelste ist, aber neuen Leuten eine starke Anleitung bietet, die möglicherweise für die eindeutige Zuordnung zur Problemdomäne dankbar sind.


-1

Vererbung ist nicht so wichtig wie früher gedacht. Es ist immer noch wichtig , und es zu entfernen wäre ein schwerer Fehler.

Ein extremes Beispiel ist Objective-C, bei dem alles eine Unterklasse von NSObject ist (der Compiler erlaubt Ihnen nicht, eine Klasse zu deklarieren, die keine Basisklasse hat, daher muss alles , was nicht in den Compiler integriert ist, eine Unterklasse von etwas sein in den Compiler eingebaut). In NSObject sind viele nützliche Dinge integriert, die Sie nicht verpassen müssen.

Ein weiteres interessantes Beispiel ist UIView, die grundlegende "view" -Klasse in UIKit für die iOS-Entwicklung. Dies ist eine Klasse, die einerseits einer abstrakten Klasse ähnelt, die Funktionen deklariert, die Unterklassen ausfüllen sollen, aber auch für sich allein nützlich ist. Es verfügt über von UIKit gelieferte Unterklassen, die häufig unverändert verwendet werden. Der Entwickler installiert Unteransichten in einer Ansicht. Und es gibt oft von Entwicklern definierte Unterklassen, die häufig Kompositionen verwenden. Es gibt keine strengen Einzelregeln oder gar Regeln. Sie verwenden alles, was Ihren Anforderungen am besten entspricht.


Ihr "extremes Beispiel" ist ungefähr, wie die meisten modernen OO-Sprachen funktionieren: Python-Unterklassen type, von denen jedes Nicht-Primitiv in Java erbt Object, alles in JavaScript hat einen Prototyp, der mit endet Objectusw.
Delioth

-1

[Ich riskiere eine freche Antwort auf die Titelfrage.]

Können wir die Vererbung mithilfe von Strategiemustern und Abhängigkeitsinjektion vollständig ersetzen?

Ja ... außer dass das Strategiemuster selbst die Vererbung verwendet. Das Strategiemuster arbeitet mit der Schnittstellenvererbung. Durch das Ersetzen der Vererbung durch Komposition + Strategie wird die Vererbung an einen anderen Ort verschoben. Trotzdem lohnt sich ein solcher Ersatz oft, weil er es ermöglicht, Hierarchien auseinanderzuhalten .


2
" Das Strategiemuster arbeitet mit der Schnittstellenvererbung ". Denken Sie daran, dass das Strategiemuster ein Entwurfsmuster ist. kein Implementierungsmuster. Es kann also unter Verwendung von Schnittstellen implementiert werden, aber es kann auch unter Verwendung beispielsweise eines Hashs / ​​Wörterbuchs von Funktionen implementiert werden.
David Arno

3
Die Schnittstellenvererbung ist überhaupt keine Vererbung, sondern nur eine Art Vertrag oder Klassifikator. Sie können Delegaten auch für Strategiemuster verwenden (ich bevorzuge sie).
Deepak Mishra

Plus eins, Alter: ... arbeitet an der Vererbung von Schnittstellen , ein großes Lob an die ordnungsgemäße Verwendung des allgemeinen Begriffs "Schnittstelle". Ich denke, schlechtes oder inkompetentes Klassendesign ist der Hauptgrund, warum diese Frage aufgeworfen wird. Zu erklären, warum / wie ein Buch nehmen würde; Der Teufel steckt im Detail. Ich habe mit Vererbungsentwürfen gearbeitet, die eine Freude waren und gleichzeitig eine tiefe Vererbung, die die Hölle war. Ich habe Craptastic interfaceDesign gesehen, das mit Vererbung repariert wurde . Das versteckte Problem hierbei ist das inkonsistente, vom Morphing abhängige Verhalten der Implementierung. P, S. Vererbung und Schnittstelle schließen sich nicht gegenseitig aus
Radarbob

@ DavidArno-Kommentar : Dieser Kommentar wäre in Ordnung, wenn er an die OP-Frage angehängt würde. Die OP-Frage ist technisch falsch formuliert, aber wir bekommen sie immer noch. Das Problem ist nicht diese spezielle Antwort.
Radarbob

@ DeepakMishra Kommentar : . Eine "Schnittstelle" sind alle öffentlichen Mitglieder einer Klasse. Leider ist die Bedeutung "Schnittstelle" überladen. Wir müssen darauf achten, die allgemeine Bedeutung mit dem Schlüsselwort einer Programmiersprache zu unterscheideninterface
radarbob

-2

Nein. Wenn eine Stockente andere Parameter zum Instanziieren benötigt als eine andere Ententyp, wäre es ein Albtraum, sie zu ändern. Sollten Sie auch in der Lage sein, eine Ente zu instanziieren? Es ist eher eine Stockente oder eine Mandarinenente oder eine andere Art von Enten, die Sie haben.

Außerdem möchte ich vielleicht eine Logik für die Typen, damit eine Typhierarchie besser ist.

Wenn die Wiederverwendung von Code für einige Funktionen problematisch wird, können wir sie erstellen, indem wir die Funktionen über den Konstruktor übergeben.

Auch hier kommt es wirklich auf Ihren Anwendungsfall an. Wenn Sie nur eine Ente und eine Stockente haben, ist eine Klassenhierarchie eine viel einfachere Lösung.

Hier ist ein Beispiel, in dem Sie das Strategiemuster verwenden möchten: Wenn Sie Kundenklassen haben und eine Abrechnungsstrategie (Schnittstelle) übergeben möchten, die eine Happy-Hour-Abrechnungsstrategie oder eine reguläre Abrechnungsstrategie sein kann, können Sie diese übergeben im Konstruktor. Auf diese Weise müssen Sie keine zwei verschiedenen Kundenklassen erstellen und benötigen keine Hierarchie. Sie haben nur die eine Kundenklasse.

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.