Ich versuche, die Abhängigkeitsinjektion in einer relativ großen Anwendung zu implementieren, habe jedoch keine Erfahrung damit. Ich habe das Konzept und einige verfügbare Implementierungen von IoC- und Abhängigkeitsinjektoren wie Unity und Ninject studiert. Es gibt jedoch eine Sache, die mir entgeht. Wie soll ich die Instanzerstellung in meiner Anwendung organisieren?
Was ich überlege, ist, dass ich einige bestimmte Fabriken erstellen kann, die die Logik zum Erstellen von Objekten für einige bestimmte Klassentypen enthalten. Grundsätzlich eine statische Klasse mit einer Methode, die die Ninject Get () - Methode einer statischen Kernel-Instanz in dieser Klasse aufruft.
Wird es ein korrekter Ansatz für die Implementierung der Abhängigkeitsinjektion in meiner Anwendung sein, oder sollte ich ihn nach einem anderen Prinzip implementieren?
quelle
Antworten:
Denken Sie noch nicht an das Tool, das Sie verwenden werden. Sie können DI ohne einen IoC-Container ausführen.
Erster Punkt: Mark Seemann hat ein sehr gutes Buch über DI in .Net
Zweitens: Kompositionswurzel. Stellen Sie sicher, dass die gesamte Einrichtung am Einstiegspunkt des Projekts erfolgt ist. Der Rest Ihres Codes sollte sich mit Injektionen auskennen und nicht mit den verwendeten Tools.
Drittens: Konstruktoreinspritzung ist der wahrscheinlichste Weg (es gibt Fälle, in denen Sie es nicht wollen, aber nicht so viele).
Viertens: Versuchen Sie, Lambda-Fabriken und ähnliche Funktionen zu verwenden, um zu vermeiden, dass nicht benötigte Interfaces / Klassen nur zum Zweck der Injektion erstellt werden.
quelle
Ihre Frage besteht aus zwei Teilen: der richtigen Implementierung von DI und der Umgestaltung einer großen Anwendung für die Verwendung von DI.
Der erste Teil wird von @Miyamoto Akira gut beantwortet (insbesondere die Empfehlung, Mark Seemanns Buch "Dependency Injection in .net" zu lesen. Marks Blog ist auch eine gute kostenlose Ressource.
Der zweite Teil ist um einiges komplizierter.
Ein guter erster Schritt wäre, einfach alle Instanziierungen in die Klassenkonstruktoren zu verschieben - ohne die Abhängigkeiten zu injizieren, sondern nur sicherzustellen, dass Sie nur
new
den Konstruktor aufrufen .Dadurch werden alle SRP-Verstöße hervorgehoben, die Sie begangen haben, sodass Sie die Klasse in kleinere Mitarbeiter aufteilen können.
Die nächste Ausgabe, die Sie finden werden, sind Klassen, deren Konstruktion auf Laufzeitparametern basiert. Sie können dies in der Regel beheben, indem Sie einfache Factorys erstellen, häufig mit
Func<param,type>
, diese im Konstruktor initialisieren und in den Methoden aufrufen.Der nächste Schritt besteht darin, Schnittstellen für Ihre Abhängigkeiten zu erstellen und Ihren Klassen einen zweiten Konstruktor hinzuzufügen, der diese Schnittstellen ausschließt. Ihr parameterloser Konstruktor würde die konkreten Instanzen neu erstellen und an den neuen Konstruktor übergeben. Dies wird allgemein als "B * stard Injection" oder "Poor mans DI" bezeichnet.
Dies gibt Ihnen die Möglichkeit, einige Unit-Tests durchzuführen, und wenn dies das Hauptziel des Refactors war, können Sie dort anhalten. Neuer Code wird mit Konstruktorinjektion geschrieben, aber Ihr alter Code kann weiterhin wie geschrieben funktionieren, ist jedoch noch testbar.
Sie können natürlich weiter gehen. Wenn Sie beabsichtigen, einen IOC-Container zu verwenden, besteht ein nächster Schritt möglicherweise darin, alle direkten Aufrufe
new
in Ihren parameterlosen Konstruktoren durch statische Aufrufe des IOC-Containers zu ersetzen und diesen im Wesentlichen (ab) als Service-Locator zu verwenden.Dadurch werden mehr Fälle von Laufzeitkonstruktorparametern ausgelöst, die wie zuvor behandelt werden müssen.
Sobald dies erledigt ist, können Sie damit beginnen, die parameterlosen Konstruktoren zu entfernen und zu reinem DI umzugestalten.
Letztendlich wird dies eine Menge Arbeit sein, also stellen Sie sicher, dass Sie entscheiden, warum Sie es tun möchten, und priorisieren Sie die Teile der Codebasis, die am meisten vom Refactor profitieren
quelle
Zunächst möchte ich erwähnen, dass Sie sich dies erheblich erschweren, indem Sie ein vorhandenes Projekt umgestalten, anstatt ein neues Projekt zu starten.
Sie sagten, es sei eine große Anwendung, wählen Sie also zunächst eine kleine Komponente aus. Vorzugsweise eine "Blattknoten" -Komponente, die von nichts anderem verwendet wird. Ich weiß nicht, wie der Status der automatisierten Tests in dieser Anwendung ist, aber Sie brechen alle Komponententests für diese Komponente ab. Also sei darauf vorbereitet. In Schritt 0 werden Integrationstests für die zu ändernde Komponente geschrieben, sofern diese noch nicht vorhanden sind. Als letzte Möglichkeit (keine Testinfrastruktur; kein Buy-In zum Schreiben) sollten Sie eine Reihe manueller Tests durchführen, um zu überprüfen, ob diese Komponente funktioniert.
Die einfachste Möglichkeit, Ihr Ziel für den DI-Refactor anzugeben, besteht darin, alle Instanzen des Operators 'new' aus dieser Komponente zu entfernen. Diese fallen im Allgemeinen in zwei Kategorien:
Invariante Mitgliedsvariable: Dies sind Variablen, die einmal festgelegt werden (normalerweise im Konstruktor) und für die Lebensdauer des Objekts nicht neu zugewiesen werden. Für diese können Sie eine Instanz des Objekts in den Konstruktor einfügen. Sie sind im Allgemeinen nicht für die Entsorgung dieser Objekte verantwortlich (ich möchte hier nicht sagen, niemals, aber Sie sollten diese Verantwortung wirklich nicht haben).
Variant-Member-Variable / Methodenvariable: Dies sind Variablen, bei denen zu einem bestimmten Zeitpunkt während der Lebensdauer des Objekts Datenmüll gesammelt wird. In diesen Fällen möchten Sie Ihrer Klasse eine Factory hinzufügen, um diese Instanzen bereitzustellen. Sie sind für die Entsorgung von Objekten verantwortlich, die von einer Fabrik erstellt wurden.
Ihr IoC-Container (ninject, wie es sich anhört) übernimmt die Verantwortung für die Instanziierung dieser Objekte und die Implementierung Ihrer Factory-Schnittstellen. Was auch immer die Komponente verwendet, die Sie geändert haben, muss über den IoC-Container informiert sein, damit er Ihre Komponente abrufen kann.
Sobald Sie die oben genannten Schritte ausgeführt haben, können Sie alle Vorteile nutzen, die Sie von DI in Ihrer ausgewählten Komponente erhoffen. Jetzt wäre ein guter Zeitpunkt, um diese Komponententests hinzuzufügen / zu korrigieren. Wenn es Unit-Tests gegeben hat, müssen Sie entscheiden, ob Sie sie durch Injizieren realer Objekte zusammenfügen oder neue Unit-Tests mit Hilfe von Mocks schreiben möchten.
Wiederholen Sie einfach die obigen Schritte für jede Komponente Ihrer Anwendung, und verschieben Sie dabei den Verweis auf den IoC-Container nach oben, bis nur noch der Hauptteil davon wissen muss.
quelle
Richtiger Ansatz ist es, Konstruktorinjektion zu verwenden, wenn Sie verwenden
Dann haben Sie den Service Locator als Abhängigkeitsinjektion.
quelle
Sie sagen, Sie möchten es verwenden, geben aber nicht an, warum.
DI ist nichts weiter als ein Mechanismus zum Erzeugen von Konkretionen aus Schnittstellen.
Dies an sich ergibt sich aus dem DIP . Wenn Ihr Code bereits in diesem Stil geschrieben ist und Sie eine einzige Stelle haben, an der Konkretisierungen generiert werden, bringt DI der Party nichts mehr. Das Hinzufügen von DI-Framework-Code würde Ihre Codebasis einfach aufblähen und verschleiern.
Angenommen , Sie haben es verwenden möchten, richten Sie Sie in der Regel die Fabrik / builder / Container (oder was auch immer) früh in der Anwendung , so dass es deutlich sichtbar ist.
NB, es ist sehr einfach, eigene zu rollen, wenn Sie möchten, anstatt sich auf Ninject / StructureMap oder was auch immer festzulegen. Wenn Sie jedoch eine angemessene Fluktuation haben, kann dies dazu führen, dass die Räder mit einem anerkannten Rahmen geschmiert werden oder zumindest so geschrieben werden, dass die Lernkurve nicht zu lang wird.
quelle
Eigentlich ist der "richtige" Weg, KEINE Fabrik zu benutzen, es sei denn, es gibt absolut keine andere Wahl (wie bei Unit-Tests und bestimmten Mocks - für Produktionscode verwenden Sie KEINE Fabrik)! Dies ist eigentlich ein Anti-Pattern und sollte unter allen Umständen vermieden werden. Der springende Punkt hinter einem DI-Container ist, dass das Gadget die Arbeit für Sie erledigt.
Wie bereits in einem früheren Beitrag erwähnt, soll Ihr IoC-Gadget die Verantwortung für die Erstellung der verschiedenen abhängigen Objekte in Ihrer App übernehmen. Das heißt, Sie lassen Ihr DI-Gadget die verschiedenen Instanzen selbst erstellen und verwalten. Dies ist der springende Punkt hinter DI - Ihre Objekte sollten NIEMALS wissen, wie sie die Objekte erstellen und / oder verwalten, von denen sie abhängen. Andernfalls bricht die Kupplung ab.
Das Konvertieren einer vorhandenen Anwendung in alle DIs ist ein großer Schritt. Abgesehen von den offensichtlichen Schwierigkeiten sollten Sie jedoch auch (um Ihr Leben ein wenig zu vereinfachen) ein DI-Tool ausprobieren, das den Großteil Ihrer Bindungen automatisch ausführt (Der Kern von Ninject sind die
"kernel.Bind<someInterface>().To<someConcreteClass>()"
Aufrufe, die Sie ausführen , um Ihre Schnittstellendeklarationen mit den konkreten Klassen abzugleichen , die Sie zum Implementieren dieser Schnittstellen verwenden möchten. Es sind diese "Bind" -Aufrufe, mit denen Ihr DI-Gadget Ihre Konstruktoraufrufe abfangen und die bereitstellen kann Erforderliche abhängige Objektinstanzen Ein typischer Konstruktor (Pseudocode hier gezeigt) für eine Klasse könnte sein:Beachten Sie, dass sich an keiner Stelle in diesem Code ein Code befand, der die Instanz von SomeConcreteClassA oder SomeOtherConcreteClassB erstellt / verwaltet / freigegeben hat. Tatsächlich wurde keine konkrete Klasse erwähnt. Also ... wo ist die Magie passiert?
Im Start-Teil Ihrer App hat Folgendes stattgefunden (dies ist wiederum Pseudo-Code, aber er kommt dem echten (Ninject-) Ding ziemlich nahe ...):
Dieser kleine Code fordert das Ninject-Gadget auf, nach Konstruktoren zu suchen, diese zu scannen, nach Instanzen von Schnittstellen zu suchen, für die es konfiguriert wurde (das sind die "Bind" -Aufrufe), und dann eine Instanz der konkreten Klasse überall zu erstellen und zu ersetzen Die Instanz wird referenziert.
Es gibt ein nettes Tool, das Ninject sehr gut ergänzt und Ninject.Extensions.Conventions (ein weiteres NuGet-Paket) heißt und den Großteil dieser Arbeit für Sie erledigt. Nicht von den hervorragenden Lernerfahrungen abzulenken, die Sie während des Aufbaus selbst miterleben werden, aber um sich an die Arbeit zu machen, ist dies möglicherweise ein Hilfsmittel, um dies zu untersuchen.
Wenn Speicher zur Verfügung steht, verfügt Unity (offiziell von Microsoft, jetzt ein Open Source-Projekt) über einen oder zwei Methodenaufrufe, die dasselbe tun. Andere Tools verfügen über ähnliche Hilfsprogramme.
Welchen Weg Sie auch wählen, lesen Sie unbedingt Mark Seemanns Buch für den Großteil Ihres DI-Trainings. Es sollte jedoch darauf hingewiesen werden, dass selbst die "Großen" der Welt der Softwaretechnik (wie Mark) grelle Fehler machen können - Mark hat alles vergessen Ninject in seinem Buch. Hier ist eine weitere Ressource, die nur für Ninject geschrieben wurde. Ich habe es und es ist eine gute Lektüre: Mastering Ninject for Dependency Injection
quelle
Es gibt keinen "richtigen Weg", aber es gibt ein paar einfache Prinzipien, denen man folgen muss:
Das ist alles. Sicher, das sind Prinzipien, keine Gesetze, aber wenn Sie sie befolgen, können Sie sicher sein, dass Sie DI tun (bitte korrigieren Sie mich, wenn ich falsch liege).
Wie kann man also zur Laufzeit Objekte erstellen, ohne "neu" zu sein und ohne den DI-Container zu kennen?
Im Falle von NInject gibt es eine Factory-Erweiterung , mit der Fabriken erstellt werden können. Sicher, die erstellten Fabriken haben immer noch eine interne Referenz zum Kernel, aber auf diese kann in Ihrer Anwendung nicht zugegriffen werden.
quelle