Abhängigkeitsinjektion durch Konstruktoren oder Eigenschaftssetzer?

151

Ich überarbeite eine Klasse und füge ihr eine neue Abhängigkeit hinzu. Die Klasse übernimmt derzeit ihre vorhandenen Abhängigkeiten im Konstruktor. Aus Gründen der Konsistenz füge ich den Parameter dem Konstruktor hinzu.
Natürlich gibt es ein paar Unterklassen und noch mehr für Unit-Tests, also spiele ich jetzt das Spiel, alle Konstruktoren so zu ändern, dass sie übereinstimmen, und es dauert ewig.
Ich denke, dass die Verwendung von Eigenschaften mit Setzern ein besserer Weg ist, um Abhängigkeiten zu erhalten. Ich denke nicht, dass injizierte Abhängigkeiten Teil der Schnittstelle zum Erstellen einer Instanz einer Klasse sein sollten. Sie fügen eine Abhängigkeit hinzu und jetzt wissen plötzlich alle Ihre Benutzer (Unterklassen und alle, die Sie direkt instanziieren) davon. Das fühlt sich wie eine Unterbrechung der Kapselung an.

Dies scheint nicht das Muster mit dem hier vorhandenen Code zu sein, daher möchte ich herausfinden, was der allgemeine Konsens ist, welche Vor- und Nachteile Konstruktoren gegenüber Eigenschaften haben. Ist die Verwendung von Eigenschaftssetzern besser?

Niall Connaughton
quelle

Antworten:

126

Es hängt davon ab :-).

Wenn die Klasse ihre Arbeit ohne die Abhängigkeit nicht ausführen kann, fügen Sie sie dem Konstruktor hinzu. Die Klasse benötigt die neue Abhängigkeit, damit Ihre Änderung die Dinge kaputt macht. Das Erstellen einer Klasse, die nicht vollständig initialisiert ist ("zweistufige Konstruktion"), ist ein Anti-Pattern (IMHO).

Wenn die Klasse ohne die Abhängigkeit arbeiten kann, ist ein Setter in Ordnung.

sleske
quelle
10
Ich denke, es ist in vielen Fällen vorzuziehen, das Null-Objekt-Muster zu verwenden und die Referenzen auf dem Konstruktor zu fordern. Dies vermeidet jegliche Nullprüfung und die erhöhte zyklomatische Komplexität.
Mark Lindell
3
@ Mark: Guter Punkt. Die Frage bestand jedoch darin, einer vorhandenen Klasse eine Abhängigkeit hinzuzufügen. Wenn Sie dann einen Konstruktor ohne Argumente beibehalten, ist die Abwärtskompatibilität gewährleistet.
Sleske
Was ist, wenn die Abhängigkeit benötigt wird, um zu funktionieren, aber eine Standardinjektion dieser Abhängigkeit reicht normalerweise aus? Sollte diese Abhängigkeit dann durch eine Eigenschaft oder eine Konstruktorüberladung "überschreibbar" sein?
Patrick Szalapski
@Patrick: Mit "Die Klasse kann ihre Arbeit ohne die Abhängigkeit nicht erledigen" habe ich gemeint, dass es keinen vernünftigen Standard gibt (z. B. erfordert die Klasse eine DB-Verbindung). In Ihrer Situation würden beide funktionieren. Normalerweise würde ich mich immer noch für den Konstruktoransatz entscheiden, weil er die Komplexität verringert (was ist, wenn z. B. der Setter zweimal aufgerufen wird)?
Sleske
1
Eine gute Zusammenfassung hierzu finden Sie hier unterjava.com/different-types-of-bean-injection-in-spring
Eldhose Abraham
21

Die Benutzer einer Klasse sollen über die Abhängigkeiten einer bestimmten Klasse Bescheid wissen. Wenn ich eine Klasse hätte, die beispielsweise mit einer Datenbank verbunden ist und keine Möglichkeit bietet, eine Persistenzschichtabhängigkeit einzufügen, würde ein Benutzer niemals wissen, dass eine Verbindung zur Datenbank verfügbar sein muss. Wenn ich jedoch den Konstruktor ändere, lasse ich die Benutzer wissen, dass eine Abhängigkeit von der Persistenzschicht besteht.

Um zu verhindern, dass Sie jede Verwendung des alten Konstruktors ändern müssen, wenden Sie einfach die Konstruktorkettung als temporäre Brücke zwischen dem alten und dem neuen Konstruktor an.

public class ClassExample
{
    public ClassExample(IDependencyOne dependencyOne, IDependencyTwo dependencyTwo)
        : this (dependnecyOne, dependencyTwo, new DependnecyThreeConcreteImpl())
    { }

    public ClassExample(IDependencyOne dependencyOne, IDependencyTwo dependencyTwo, IDependencyThree dependencyThree)
    {
        // Set the properties here.
    }
}

Einer der Punkte der Abhängigkeitsinjektion besteht darin, aufzuzeigen, welche Abhängigkeiten die Klasse hat. Wenn die Klasse zu viele Abhängigkeiten aufweist, kann es Zeit für ein Refactoring sein: Verwendet jede Methode der Klasse alle Abhängigkeiten? Wenn nicht, ist dies ein guter Ausgangspunkt, um zu sehen, wo die Klasse aufgeteilt werden könnte.

Doktor Blue
quelle
Natürlich funktioniert die Konstruktorkettung nur, wenn für den neuen Parameter ein angemessener Standardwert vorhanden ist. Aber sonst kann man es sowieso nicht vermeiden, Dinge zu zerbrechen ...
Sleske
Normalerweise verwenden Sie alles, was Sie in der Methode vor der Abhängigkeitsinjektion verwendet haben, als Standardparameter. Im Idealfall würde dies die neue Konstruktoraddition zu einem sauberen Refactoring machen, da sich das Verhalten der Klasse nicht ändern würde.
Doctor Blue
1
Ich verstehe Ihren Standpunkt zur Ressourcenverwaltung von Abhängigkeiten wie Datenbankverbindungen. Ich denke, das Problem in meinem Fall ist, dass die Klasse, zu der ich eine Abhängigkeit hinzufüge, mehrere Unterklassen hat. In einer IOC-Containerwelt, in der die Eigenschaft vom Container festgelegt wird, würde die Verwendung des Setters zumindest den Druck auf die Duplizierung der Konstruktorschnittstelle zwischen allen Unterklassen verringern.
Niall Connaughton
17

Wenn Sie den Konstruktor aktivieren, können Sie natürlich alle auf einmal validieren. Wenn Sie Dinge in schreibgeschützten Feldern zuweisen, haben Sie ab der Erstellungszeit einige Garantien für die Abhängigkeiten Ihres Objekts.

Es ist ein echtes Problem, neue Abhängigkeiten hinzuzufügen, aber zumindest auf diese Weise beschwert sich der Compiler so lange, bis es korrekt ist. Welches ist eine gute Sache, denke ich.

Joe
quelle
Plus eins dafür. Darüber hinaus reduziert dies die Gefahr von zirkulären Abhängigkeiten extrem ...
Gilgamash
11

Wenn Sie eine große Anzahl optionaler Abhängigkeiten haben (was bereits ein Geruch ist), ist wahrscheinlich die Setter-Injektion der richtige Weg. Die Konstruktorinjektion zeigt jedoch besser Ihre Abhängigkeiten.

Epitka
quelle
11

Der allgemein bevorzugte Ansatz besteht darin, die Konstruktorinjektion so weit wie möglich zu verwenden.

Die Konstruktorinjektion gibt genau an, welche Abhängigkeiten erforderlich sind, damit das Objekt ordnungsgemäß funktioniert. Nichts ist ärgerlicher, als ein Objekt neu zu erstellen und es beim Aufrufen einer Methode zum Absturz zu bringen, da einige Abhängigkeiten nicht festgelegt sind. Das von einem Konstruktor zurückgegebene Objekt sollte sich in einem funktionierenden Zustand befinden.

Versuchen Sie, nur einen Konstruktor zu haben. Dies hält das Design einfach und vermeidet Mehrdeutigkeiten (wenn nicht für Menschen, für den DI-Container).

Sie können die Eigenschaftsinjektion verwenden, wenn Sie das haben, was Mark Seemann als lokalen Standard bezeichnet in seinem Buch "Abhängigkeitsinjektion in .NET" : Die Abhängigkeit ist optional, da Sie eine gut funktionierende Implementierung bereitstellen können, dem Aufrufer jedoch erlauben möchten, eine andere anzugeben, wenn erforderlich.

(Frühere Antwort unten)


Ich denke, dass die Konstruktorinjektion besser ist, wenn die Injektion obligatorisch ist. Wenn dadurch zu viele Konstruktoren hinzugefügt werden, sollten Sie Fabriken anstelle von Konstruktoren verwenden.

Die Setter-Injektion ist gut, wenn die Injektion optional ist oder wenn Sie sie auf halbem Weg ändern möchten. Ich mag Setter im Allgemeinen nicht, aber es ist Geschmackssache.

Philippe
quelle
Ich würde argumentieren, dass das Ändern der Injektion auf halbem Weg normalerweise ein schlechter Stil ist (weil Sie Ihrem Objekt einen verborgenen Zustand hinzufügen). Aber natürlich keine Regel ohne Ausnahme ...
Sleske
Ja, deshalb habe ich gesagt, dass ich Setter nicht besonders mag ... Ich mag den Konstruktor-Ansatz, da er dann nicht geändert werden kann.
Philippe
"Wenn dadurch zu viele Konstruktoren hinzugefügt werden, sollten Sie Fabriken anstelle von Konstruktoren verwenden." Grundsätzlich verschieben Sie alle Laufzeitausnahmen und können sogar Fehler verursachen und am Ende bei einer Service Locator-Implementierung hängen bleiben.
MeTitus
@Marco das ist meine frühere Antwort und du hast recht. Wenn es viele Konstruktoren gibt, würde ich argumentieren, dass die Klasse zu viele Dinge tut :-) Oder eine abstrakte Fabrik in Betracht ziehen.
Philippe
7

Es ist größtenteils eine Frage des persönlichen Geschmacks. Persönlich bevorzuge ich die Setter-Injektion, weil ich glaube, dass sie Ihnen mehr Flexibilität bietet, wenn Sie Implementierungen zur Laufzeit ersetzen können. Darüber hinaus sind Konstruktoren mit vielen Argumenten meiner Meinung nach nicht sauber, und die in einem Konstruktor angegebenen Argumente sollten auf nicht optionale Argumente beschränkt sein.

Solange die Klassenschnittstelle (API) klar ist, was sie zur Ausführung ihrer Aufgabe benötigt, sind Sie gut.

nkr1pt
quelle
Bitte geben Sie an, warum Sie abstimmen.
nkr1pt
3
Ja, Konstruktoren mit vielen Argumenten sind schlecht. Deshalb refaktorieren Sie Klassen mit vielen Konstruktorparametern :-).
Sleske
10
@ nkr1pt: Die meisten Leute (ich eingeschlossen) stimmen zu, dass die Setter-Injektion schlecht ist, wenn Sie damit eine Klasse erstellen können, die zur Laufzeit fehlschlägt, wenn die Injektion nicht erfolgt. Ich glaube, jemand hat daher Einwände gegen Ihre Aussage erhoben, es sei persönlicher Geschmack.
Sleske
7

Ich persönlich bevorzuge das "Muster" " Extrahieren und Überschreiben " gegenüber dem Einfügen von Abhängigkeiten in den Konstruktor, hauptsächlich aus dem in Ihrer Frage dargelegten Grund. Sie können die Eigenschaften als festlegen virtualund dann die Implementierung in einer abgeleiteten testbaren Klasse überschreiben.

Russ Cam
quelle
1
Ich denke, der offizielle Name des Musters ist "Template Method".
Davidjnelson
6

Ich bevorzuge die Konstruktorinjektion, weil sie dazu beiträgt, die Abhängigkeitsanforderungen einer Klasse zu "erzwingen". Wenn es in der c'tor ist ein Verbraucher hat die Objekte , um den App Kompilierung zu erhalten. Wenn Sie die Setter-Injektion verwenden, wissen sie möglicherweise erst zur Laufzeit, dass sie ein Problem haben - und je nach Objekt kann es zu spät zur Laufzeit kommen.

Ich verwende immer noch von Zeit zu Zeit die Setter-Injektion, wenn das injizierte Objekt möglicherweise selbst eine Menge Arbeit benötigt, wie z. B. die Initialisierung.

ctacke
quelle
6

Ich bevorzuge die Konstruktorinjektion, weil dies am logischsten erscheint. Es ist so, als würde ich sagen, dass meine Klasse diese Abhängigkeiten benötigt , um ihre Arbeit zu erledigen. Wenn es sich um eine optionale Abhängigkeit handelt, erscheinen die Eigenschaften vernünftig.

Ich verwende die Eigenschaftsinjektion auch zum Festlegen von Dingen, auf die der Container keinen Verweis hat, z. B. eine ASP.NET-Ansicht in einem mit dem Container erstellten Präsentator.

Ich glaube nicht, dass es die Kapselung bricht. Das Innenleben sollte intern bleiben und die Abhängigkeiten befassen sich mit einem anderen Anliegen.

David Kiff
quelle
Danke für deine Antwort. Es scheint sicher, dass der Konstruktor die populäre Antwort ist. Ich denke jedoch, dass es die Kapselung auf irgendeine Weise unterbricht. Vor der Abhängigkeitsinjektion deklarierte und instanziierte die Klasse alle konkreten Typen, die sie für ihre Arbeit benötigte. Mit DI wissen Unterklassen (und alle manuellen Instanziatoren) jetzt, welche Tools eine Basisklasse verwendet. Sie fügen eine neue Abhängigkeit hinzu und müssen nun die Instanz aus allen Unterklassen verketten, auch wenn diese die Abhängigkeit nicht selbst verwenden müssen.
Niall Connaughton
Habe gerade eine schöne lange Antwort geschrieben und sie aufgrund einer Ausnahme auf dieser Seite verloren !! :( Zusammenfassend lässt sich sagen, dass die Basisklasse normalerweise zur Wiederverwendung von Logik verwendet wird. Diese Logik kann recht leicht in die Unterklasse aufgenommen werden. Sie können sich also die Basisklasse und die Unterklasse = ein Problem vorstellen, das von mehreren externen Objekten abhängt
David Kiff
3

Eine Option, die in Betracht gezogen werden könnte, ist das Zusammensetzen komplexer Mehrfachabhängigkeiten aus einfachen Einzelabhängigkeiten. Definieren Sie zusätzliche Klassen für zusammengesetzte Abhängigkeiten. Dies erleichtert die Injektion von WRT-Konstruktoren ein wenig - weniger Parameter pro Aufruf - und behält gleichzeitig die Notwendigkeit bei, alle Abhängigkeiten zu liefern, um sie zu instanziieren.

Am sinnvollsten ist es natürlich, wenn es eine logische Gruppierung von Abhängigkeiten gibt, sodass die Verbindung mehr als ein beliebiges Aggregat ist, und es ist am sinnvollsten, wenn es mehrere Abhängigkeiten für eine einzelne zusammengesetzte Abhängigkeit gibt - aber der Parameterblock "Muster" hat Ich bin schon lange da und die meisten, die ich gesehen habe, waren ziemlich willkürlich.

Persönlich bin ich jedoch eher ein Fan der Verwendung von Methoden / Eigenschaftssetzern, um Abhängigkeiten, Optionen usw. anzugeben. Die Anrufnamen beschreiben, was vor sich geht. Es ist jedoch eine gute Idee, ein Beispiel für das Einrichten von Snippets anzugeben und sicherzustellen, dass die abhängige Klasse genügend Fehlerprüfungen durchführt. Möglicherweise möchten Sie ein Finite-State-Modell für das Setup verwenden.

Steve314
quelle
3

Ich war kürzlich in einer Situation, in der ich mehrere Abhängigkeiten in einer Klasse hatte, aber nur eine der Abhängigkeiten musste sich in jeder Implementierung ändern. Da die Abhängigkeiten von Datenzugriff und Fehlerprotokollierung wahrscheinlich nur zu Testzwecken geändert werden, habe ich optionale Parameter hinzugefügt für diese Abhängigkeiten und Standardimplementierungen dieser Abhängigkeiten in meinem Konstruktorcode bereitgestellt. Auf diese Weise behält die Klasse ihr Standardverhalten bei, sofern es nicht vom Verbraucher der Klasse überschrieben wird.

Die Verwendung optionaler Parameter kann nur in Frameworks durchgeführt werden, die sie unterstützen, wie z. B. .NET 4 (sowohl für C # als auch für VB.NET, obwohl VB.NET sie immer hatte). Natürlich können Sie ähnliche Funktionen erreichen, indem Sie einfach eine Eigenschaft verwenden, die vom Konsumenten Ihrer Klasse neu zugewiesen werden kann. Sie erhalten jedoch nicht den Vorteil der Unveränderlichkeit, wenn ein privates Schnittstellenobjekt einem Parameter des Konstruktors zugewiesen wird.

Wenn Sie jedoch eine neue Abhängigkeit einführen, die von jedem Verbraucher bereitgestellt werden muss, müssen Sie Ihren Konstruktor und den gesamten Code, der Ihre Klasse verwendet, umgestalten. Meine obigen Vorschläge gelten wirklich nur, wenn Sie den Luxus haben, eine Standardimplementierung für Ihren gesamten aktuellen Code bereitzustellen, aber dennoch die Möglichkeit bieten, die Standardimplementierung bei Bedarf zu überschreiben.

Ben McCormack
quelle
1

Dies ist ein alter Beitrag, aber wenn er in Zukunft benötigt wird, ist dies möglicherweise von Nutzen:

https://github.com/omegamit6zeichen/prinject

Ich hatte eine ähnliche Idee und kam auf diesen Rahmen. Es ist wahrscheinlich alles andere als vollständig, aber es ist eine Idee eines Frameworks, das sich auf die Eigenschaftsinjektion konzentriert

Markus
quelle
0

Dies hängt davon ab, wie Sie implementieren möchten. Ich bevorzuge die Konstruktorinjektion überall dort, wo ich der Meinung bin, dass sich die Werte, die in die Implementierung einfließen, nicht oft ändern. Beispiel: Wenn die Compnay-Strategie mit Oracle Server verbunden ist, konfiguriere ich meine Datenquellenwerte für eine Bean, die Verbindungen über Konstruktorinjektion erreicht. Andernfalls würde ich, wenn meine App ein Produkt ist und wahrscheinlich eine Verbindung zu einer beliebigen Datenbank des Kunden herstellen kann, eine solche Datenbankkonfiguration und Mehrmarkenimplementierung durch Setter-Injection implementieren. Ich habe gerade ein Beispiel genommen, aber es gibt bessere Möglichkeiten, die oben genannten Szenarien zu implementieren.

Ramarao Gangina
quelle
1
Selbst wenn ich intern codiere, codiere ich immer unter dem Gesichtspunkt, dass ich ein unabhängiger Auftragnehmer bin, der Code entwickelt, der weiterverteilbar sein soll. Ich gehe davon aus, dass es Open Source sein wird. Auf diese Weise stelle ich sicher, dass der Code modular und steckbar ist und den SOLID-Prinzipien folgt.
Fred
0

Die Konstruktorinjektion enthüllt explizit die Abhängigkeiten, wodurch Code lesbarer und weniger anfällig für nicht behandelte Laufzeitfehler wird, wenn Argumente im Konstruktor überprüft werden. Es kommt jedoch auf die persönliche Meinung an, und je mehr Sie DI verwenden, desto mehr werden Sie neigen dazu, je nach Projekt auf die eine oder andere Weise hin und her zu schwanken. Ich persönlich habe Probleme mit Code-Gerüchen wie Konstruktoren mit einer langen Liste von Argumenten, und ich bin der Meinung, dass der Verbraucher eines Objekts die Abhängigkeiten kennen sollte, um das Objekt trotzdem verwenden zu können. Daher ist dies ein Argument für die Verwendung der Eigenschaftsinjektion. Ich mag die implizite Natur der Eigenschaftsinjektion nicht, aber ich finde sie eleganter, was zu einem saubereren Code führt. Andererseits bietet die Konstruktorinjektion einen höheren Einkapselungsgrad.

Wählen Sie die Injektion nach Konstruktor oder Eigenschaft mit Bedacht aus, basierend auf Ihrem spezifischen Szenario. Und denken Sie nicht, dass Sie DI verwenden müssen, nur weil es notwendig erscheint und schlechtes Design und Code-Gerüche verhindert. Manchmal lohnt es sich nicht, ein Muster zu verwenden, wenn der Aufwand und die Komplexität den Nutzen überwiegen. Halte es einfach.

David Spenard
quelle