Sollte das Injizieren von Abhängigkeiten im ctor oder pro Methode erfolgen?

16

Erwägen:

public class CtorInjectionExample
{
    public CtorInjectionExample(ISomeRepository SomeRepositoryIn, IOtherRepository OtherRepositoryIn)
    {
        this._someRepository = SomeRepositoryIn;
        this._otherRepository = OtherRepositoryIn;
    }

    public void SomeMethod()
    {
        //use this._someRepository
    }

    public void OtherMethod()
    {
        //use this._otherRepository
    }
}

gegen:

public class MethodInjectionExample
{
    public MethodInjectionExample()
    {
    }

    public void SomeMethod(ISomeRepository SomeRepositoryIn)
    {
        //use SomeRepositoryIn
    }

    public void OtherMethod(IOtherRepository OtherRepositoryIn)
    {
        //use OtherRepositoryIn
    }
}

Während die Ctor-Injektion die Erweiterung erschwert (jeder Code, der den Ctor aufruft, muss aktualisiert werden, wenn neue Abhängigkeiten hinzugefügt werden), und die Methodenebeneninjektion scheint stärker von der Klassenebenenabhängigkeit abgekapselt zu bleiben, und ich kann keine anderen Argumente für / gegen diese Ansätze finden .

Gibt es einen endgültigen Ansatz für die Injektion?

(Hinweis: Ich habe nach Informationen dazu gesucht und versucht, diese Frage objektiv zu machen.)

StuperUser
quelle
4
Wenn Ihre Klassen zusammenhängend sind, verwenden viele verschiedene Methoden dieselben Felder. Dies sollte Ihnen die Antwort geben, die Sie suchen ...
Oded
@Oded Guter Punkt, Zusammenhalt würde die Entscheidung stark beeinflussen. Wenn es sich bei einer Klasse beispielsweise um Sammlungen von Hilfsmethoden handelt, die unterschiedliche Abhängigkeiten aufweisen (scheinbar nicht zusammenhängend, aber logisch ähnlich), und eine andere sehr zusammenhängend ist, sollten sie jeweils den vorteilhaftesten Ansatz verwenden. Dies würde jedoch zu Inkonsistenzen in einer Lösung führen.
StuperUser
Relevant für die Beispiele, die ich gegeben habe: en.wikipedia.org/wiki/False_dilemma
StuperUser

Antworten:

3

Es hängt davon ab, ob das injizierte Objekt von mehr als einer Methode in der Klasse verwendet wird und ob das Injizieren bei jeder Methode keinen Sinn ergibt. Sie müssen die Abhängigkeiten für jeden Methodenaufruf auflösen, und ob die Abhängigkeit nur von verwendet wird Eine Methode, die in den Konstruktor eingefügt wird, ist nicht gut, aber da Sie Ressourcen zuweisen, die niemals verwendet werden könnten.

nohros
quelle
15

Zunächst einmal: "Jeder Code, der den ctor aufruft, muss aktualisiert werden, wenn neue Abhängigkeiten hinzugefügt werden." Um klar zu sein, wenn Sie eine Abhängigkeitsinjektion durchführen und Code für ein Objekt mit Abhängigkeiten new () aufruft, machen Sie es falsch .

Ihr DI-Container sollte in der Lage sein, alle relevanten Abhängigkeiten einzuschleusen, sodass Sie sich keine Gedanken über das Ändern der Konstruktorsignatur machen müssen, damit dieses Argument nicht wirklich zutrifft.

Was die Idee der Injektion pro Methode im Vergleich zur Injektion pro Klasse betrifft, gibt es zwei Hauptprobleme bei der Injektion pro Methode.

Ein Problem ist, dass die Methoden Ihrer Klasse Abhängigkeiten gemeinsam nutzen sollten. Auf diese Weise können Sie sicherstellen, dass Ihre Klassen effektiv getrennt sind. Wenn Sie eine Klasse mit einer großen Anzahl von Abhängigkeiten (wahrscheinlich mehr als 4-5) sehen, ist diese Klasse ein Hauptkandidat zum Refactoring in zwei Klassen.

Das nächste Problem ist, dass Sie die Abhängigkeiten pro Methode an den Methodenaufruf übergeben müssen, um sie zu "injizieren". Dies bedeutet, dass Sie die Abhängigkeiten vor dem Methodenaufruf auflösen müssen, sodass Sie wahrscheinlich eine Menge Code wie den folgenden erhalten:

var someDependency = ServiceLocator.Resolve<ISomeDependency>();
var something = classBeingInjected.DoStuff(someDependency);

Angenommen, Sie rufen diese Methode an 10 Stellen in Ihrer Anwendung auf: Sie haben 10 dieser Snippets. Angenommen, Sie müssen DoStuff () eine weitere Abhängigkeit hinzufügen: Sie müssen dieses Snippet zehnmal ändern (oder es in eine Methode einschließen. In diesem Fall replizieren Sie das DI-Verhalten nur manuell, was eine Verschwendung ist von Zeit).

Was Sie also im Grunde dort getan haben, ist, Ihre DI-nutzenden Klassen auf ihren eigenen DI-Container aufmerksam zu machen, was eine grundsätzlich schlechte Idee ist , da dies sehr schnell zu einem klobigen Design führt, das schwer zu warten ist.

Vergleichen Sie das mit Konstruktorinjektion; Bei der Konstruktorinjektion sind Sie nicht an einen bestimmten DI-Container gebunden, und Sie sind niemals direkt dafür verantwortlich, die Abhängigkeiten Ihrer Klassen zu erfüllen, sodass die Wartung ziemlich problemlos vonstatten geht.

Es klingt für mich so, als würden Sie versuchen, IoC auf eine Klasse anzuwenden, die eine Reihe von nicht verwandten Hilfsmethoden enthält, während Sie die Hilfsklasse je nach Verwendung besser in eine Reihe von Serviceklassen aufteilen und dann die Hilfsmethode zum Delegieren der Klasse verwenden sollten Anrufe. Dies ist immer noch kein großartiger Ansatz (Helfer, die mit Methoden klassifiziert sind, die etwas Komplexeres tun als nur mit den Argumenten umzugehen, die an sie übergeben werden, sind im Allgemeinen nur schlecht geschriebene Serviceklassen), aber er wird Ihr Design zumindest ein wenig sauberer halten .

(NB Ich habe den von Ihnen vorgeschlagenen Ansatz befolgt und es war eine sehr schlechte Idee, die ich seitdem nicht wiederholt habe. Es stellte sich heraus, dass ich versuchte, Klassen zu trennen, die wirklich nicht getrennt werden mussten, und endete mit einer Reihe von Schnittstellen, bei denen jeder Methodenaufruf eine nahezu feste Auswahl anderer Schnittstellen erforderte. Es war ein Albtraum, dies beizubehalten.)

Ed James
quelle
1
"if you're doing dependency injection and you have any code calling new() on an object with dependencies, you're doing it wrong."Wenn Sie eine Methode für eine Klasse einem Komponententest unterziehen, müssen Sie sie instanziieren, oder verwenden Sie DI, um Ihren getesteten Code zu instanziieren?
StuperUser
@StuperUser Unit Testing ist ein etwas anderes Szenario. Mein Kommentar gilt nur für Seriencode. Da Sie für Ihre Tests über ein Spott-Framework Schein- (oder Stub-) Abhängigkeiten erstellen, die jeweils ein vorkonfiguriertes Verhalten sowie eine Reihe von Annahmen und Behauptungen aufweisen, müssen Sie diese von Hand einfügen.
Ed James
1
Das ist der Code, von dem ich spreche, wenn ich sage "any code calling the ctor will need updating", dass mein Produktionscode die ctors nicht aufruft. Aus diesen Gründen.
StuperUser
2
@StuperUser Gut genug, aber Sie werden immer noch die Methoden mit den erforderlichen Abhängigkeiten aufrufen, was bedeutet, dass der Rest meiner Antwort noch steht! Um ehrlich zu sein, stimme ich dem Problem mit der Konstruktorinjektion und dem Erzwingen aller Abhängigkeiten aus der Perspektive von Komponententests zu. Ich neige dazu, die Eigenschaftsinjektion zu verwenden, da Sie damit nur die Abhängigkeiten injizieren können, die Sie für einen Test benötigen. Ich finde, es handelt sich im Grunde genommen um eine weitere Gruppe impliziter Assert.WasNotCalled-Aufrufe, ohne tatsächlich Code schreiben zu müssen! : D
Ed James
3

Es gibt verschiedene Arten der Umkehrung der Kontrolle , und Ihre Frage schlägt nur zwei vor (Sie können auch factory verwenden, um die Abhängigkeit zu injizieren).

Die Antwort lautet: Es kommt auf die Bedürfnisse an. Manchmal ist es besser, eine Art und eine andere Art zu verwenden.

Ich persönlich mag Konstruktor-Injektoren, da der Konstruktor da ist, um das Objekt vollständig zu initialisieren. Wenn Sie später einen Setter aufrufen müssen, ist das Objekt nicht vollständig aufgebaut.

BЈовић
quelle
3

Sie haben den verpasst, der zumindest für mich die flexibelste Eigenschaftsinjektion ist. Ich benutze Castle / Windsor und füge meiner Klasse lediglich eine neue Auto-Eigenschaft hinzu. Ich erhalte das injizierte Objekt ohne Probleme und ohne irgendwelche Schnittstellen zu beschädigen.

Otávio Décio
quelle
Ich bin an Web-Apps gewöhnt und wenn diese Klassen in der Domain-Schicht sind, die von der Benutzeroberfläche aufgerufen wird und deren Abhängigkeiten bereits auf ähnliche Weise wie bei Property Injection aufgelöst wurden. Ich frage mich, wie ich mit Klassen umgehen soll, an die ich die injizierten Objekte weitergeben kann.
StuperUser
Ich mag auch die Eigenschaftsinjektion, damit Sie mit einem DI-Container immer noch normale Konstruktorparameter verwenden können, die automatisch zwischen aufgelösten und nicht aufgelösten Abhängigkeiten unterscheiden. Da Konstruktor- und Eigenschaftsinjektion für dieses Szenario jedoch funktional identisch sind, habe ich beschlossen, es nicht zu erwähnen;)
Ed James
Die Eigenschaftsinjektion erzwingt, dass jedes Objekt veränderlich ist, und erstellt Instanzen in einem ungültigen Zustand. Warum wäre das vorzuziehen?
Chris Pitman
2
@Chris Unter normalen Bedingungen verhalten sich die Konstruktor- und Eigenschaftsinjektion ziemlich identisch, wobei das Objekt während der Auflösung vollständig injiziert wird. Das einzige Mal, dass es als "ungültig" eingestuft werden kann, ist während des Komponententests, wenn Sie den DI-Container nicht zum Instanziieren der Implementierung verwenden. In meinem Kommentar zu meiner Antwort sehe ich, warum dies tatsächlich von Vorteil ist. Ich weiß, dass die Wandlungsfähigkeit ein Problem sein kann, aber realistisch gesehen werden Sie aufgelöste Klassen nur über ihre Schnittstellen referenzieren, wodurch die zu bearbeitenden Abhängigkeiten nicht sichtbar werden.
Ed James