Ohne Dependency injectionEdit
Im folgenden Java-Beispiel enthält die Client-Klasse eine Service-Member-Variable, die vom Client-Konstruktor initialisiert wird. Der Kunde steuert, welche Implementierung des Dienstes verwendet wird, und steuert seine Konstruktion. In dieser Situation soll der Client eine fest codierte Abhängigkeit von ExampleService haben.
// An example without dependency injectionpublic class Client { // Internal reference to the service used by this client private ExampleService service; // Constructor Client() { // Specify a specific implementation in the constructor instead of using dependency injection service = new ExampleService(); } // Method within this client that uses the services public String greet() { return "Hello " + service.getName(); }}
Dependency injection ist eine alternative Technik, um die Membervariable zu initialisieren, anstatt wie oben gezeigt explizit ein Serviceobjekt zu erstellen., Wir können dieses Beispiel anhand der verschiedenen Techniken anpassen, die in den folgenden Unterabschnitten beschrieben und veranschaulicht werden.
Typen der Abhängigkeitsinjektionedit
Es gibt mindestens drei Möglichkeiten, wie ein Clientobjekt einen Verweis auf ein externes Modul erhalten kann:
Konstruktorinjektion Die Abhängigkeiten werden über den Klassenkonstruktor eines Clients bereitgestellt. setter injection Der Client stellt eine Setter-Methode bereit, mit der der Injektor die Abhängigkeit injiziert. Schnittstelleninjektion Die Schnittstelle der Abhängigkeit bietet eine Injektormethode, mit der die Abhängigkeit in jeden an sie übergebenen Client injiziert wird., Clients müssen eine Schnittstelle implementieren, die eine Setter-Methode verfügbar macht, die die Abhängigkeit akzeptiert.
Andere typenEdit
Es ist möglich, dass DI-Frameworks andere Injektionsarten haben, die über die oben dargestellten hinausgehen.
Testframeworks können auch andere Typen verwenden. Einige moderne Testframeworks erfordern nicht einmal, dass Clients die Abhängigkeitsinjektion aktiv akzeptieren, wodurch Legacy-Code testbar wird. Insbesondere in der Java-Sprache ist es möglich, Reflection zu verwenden, um private Attribute beim Testen öffentlich zu machen und somit Injektionen durch Zuweisung zu akzeptieren.,
Einige Versuche zur Umkehrung der Kontrolle bieten keine vollständige Beseitigung der Abhängigkeit, sondern ersetzen einfach eine Form der Abhängigkeit durch eine andere. Als Faustregel gilt: Wenn ein Programmierer nur den Clientcode betrachten und mitteilen kann, welches Framework verwendet wird, ist der Client fest vom Framework abhängig.
Konstruktor injectionEdit
Für diese Methode muss der Client einen Parameter in einem Konstruktor für die Abhängigkeit angeben.
Setter injectionEdit
Für diese Methode muss der Client eine Setter-Methode für die Abhängigkeit bereitstellen.,
Interface injectionEdit
Dies ist einfach der Client, der eine Rollenschnittstelle für die Setter-Methoden der Abhängigkeiten des Clients veröffentlicht. Es kann verwendet werden, um festzustellen, wie der Injektor beim Injizieren von Abhängigkeiten mit dem Client sprechen soll.
// Service setter interface.public interface ServiceSetter { public void setService(Service service);}// Client classpublic class Client implements ServiceSetter { // Internal reference to the service used by this client. private Service service; // Set the service that this client is to use. @Override public void setService(Service service) { this.service = service; }}
Constructor injection comparisonEdit
Bevorzugt, wenn alle Abhängigkeiten zuerst erstellt werden können, da damit sichergestellt werden kann, dass sich das Clientobjekt immer in einem gültigen Zustand befindet, anstatt dass einige seiner Abhängigkeitsreferenzen null sind (nicht festgelegt werden)., Es fehlt jedoch an der Flexibilität, seine Abhängigkeiten später ändern zu lassen. Dies kann ein erster Schritt sein, um den Client unveränderlich und damit threadsicher zu machen.
Setter injection comparisonEdit
Erfordert, dass der Client für jede Abhängigkeit eine Setter-Methode bereitstellt. Dies gibt die Freiheit, den Zustand der Abhängigkeitsreferenzen jederzeit zu manipulieren. Dies bietet Flexibilität, aber wenn mehr als eine Abhängigkeit injiziert werden muss, ist es für den Client schwierig sicherzustellen, dass alle Abhängigkeiten injiziert werden, bevor der Client zur Verwendung bereitgestellt werden kann.,
Da diese Injektionen unabhängig voneinander erfolgen, kann nicht festgestellt werden, wann der Injektor mit der Verkabelung des Clients fertig ist. Eine Abhängigkeit kann einfach dadurch null bleiben, dass der Injektor seinen Setter nicht aufruft. Dies erzwingt die Überprüfung, ob die Injektion abgeschlossen wurde, ab wann der Client zusammengebaut wird, wann immer er verwendet wird.
Schnittstelleninjektion comparisonEdit
Der Vorteil der Schnittstelleninjektion besteht darin, dass Abhängigkeiten von ihren Clients völlig ignoriert werden können, jedoch immer noch einen Verweis auf einen neuen Client erhalten und mit diesem einen Verweis auf sich selbst an den Client senden können., Auf diese Weise werden die Abhängigkeiten zu Injektoren. Der Schlüssel ist, dass die Injektionsmethode (die nur eine klassische Setter-Methode sein könnte) über eine Schnittstelle bereitgestellt wird.
Zur Einführung des Clients und seiner Abhängigkeiten wird noch ein Assembler benötigt. Der Assembler würde einen Verweis auf den Client nehmen, ihn in die Setter-Schnittstelle umwandeln, die diese Abhängigkeit festlegt, und ihn an dieses Abhängigkeitsobjekt übergeben, das sich umdreht und einen Verweis auf sich selbst an den Client zurückgibt.,
Damit die Schnittstelleninjektion einen Wert hat, muss die Abhängigkeit zusätzlich zur einfachen Rückgabe eines Verweises auf sich selbst etwas tun. Dies könnte als Factory oder Sub-Assembler fungieren, um andere Abhängigkeiten aufzulösen und so einige Details vom Haupt-Assembler zu abstrahieren. Es könnte Referenzzählung sein, so dass die Abhängigkeit weiß, wie viele Clients es verwenden. Wenn die Abhängigkeit eine Sammlung von Clients verwaltet, kann sie später alle mit einer anderen Instanz von sich selbst injizieren.,
Assembling examplesEdit
Manuelles Assembling in main von Hand ist eine Möglichkeit, Dependency Injection zu implementieren.
Im obigen Beispiel wird das Objektdiagramm manuell erstellt und dann an einem Punkt aufgerufen, um mit der Arbeit zu beginnen. Wichtig zu beachten ist, dass dieser Injektor nicht rein ist. Es verwendet eines der Objekte, die es konstruiert. Es hat eine rein konstruktive Beziehung zu ExampleService, mischt jedoch Konstruktion und Verwendung des Kunden. Dies sollte nicht üblich sein. Es ist jedoch unvermeidlich., Genau wie objektorientierte Software eine nicht objektorientierte statische Methode wie main() benötigt, um loszulegen, benötigt ein abhängigkeitsinjektiertes Objektdiagramm mindestens einen (vorzugsweise nur einen) Einstiegspunkt, um das Ganze zu beginnen.
Die manuelle Konstruktion in der Hauptmethode ist möglicherweise nicht so einfach und kann auch das Aufrufen von Bauherren, Fabriken oder anderen Konstruktionsmustern beinhalten. Dies kann ziemlich fortgeschritten und abstrakt sein., Die Zeile wird von der manuellen Abhängigkeitsinjektion zur Framework-Abhängigkeitsinjektion gekreuzt, sobald der Konstruktionscode nicht mehr an die Anwendung angepasst ist und stattdessen universell ist.
Frameworks wie Spring können dieselben Objekte erstellen und miteinander verbinden, bevor ein Verweis auf den Client zurückgegeben wird. Alle Elemente des konkreten ExampleService können aus dem Code in die Konfigurationsdaten verschoben werden.
Frameworks wie Spring ermöglichen die Externalisierung von Assembly-Details in Konfigurationsdateien.Dieser Code (oben) konstruiert Objekte und verdrahtet sie gemäß Beans.xml (unten)., ExampleService ist immer noch konstruiert, obwohl es nur unten erwähnt wird. Auf diese Weise kann ein langes und komplexes Objektdiagramm definiert werden, und die einzige im Code erwähnte Klasse wäre diejenige mit der Einstiegspunktmethode, die in diesem Fall greet () ist.
Im obigen Beispiel mussten Client und Service keine Änderungen vornehmen, die von Spring bereitgestellt werden sollten. Sie dürfen einfache POJOs bleiben. Dies zeigt, wie Spring Dienste und Clients verbinden kann, die seine Existenz völlig nicht kennen. Dies konnte nicht gesagt werden, wenn den Klassen Spring-Annotationen hinzugefügt wurden., Indem Spring-spezifische Annotationen und Aufrufe unter vielen Klassen nicht verteilt werden, bleibt das System nur lose von Spring abhängig. Dies kann wichtig sein, wenn das System den Frühling überleben will.
Die Wahl, POJOs rein zu halten, kommt nicht ohne Kosten. Anstatt den Aufwand für die Entwicklung und Pflege komplexer Konfigurationsdateien aufwenden zu müssen, ist es möglich, einfach Anmerkungen zu verwenden, um Klassen zu markieren und Spring den Rest der Arbeit erledigen zu lassen. Das Auflösen von Abhängigkeiten kann einfach sein, wenn sie einer Konvention folgen, z. B. dem Abgleich nach Typ oder Namen. Dies ist die Wahl der Konvention über die Konfiguration., Es wird auch argumentiert, dass das Entfernen Framework-spezifischer Annotationen beim Refactoring auf ein anderes Framework ein trivialer Teil der Aufgabe wäre und viele Injection-Annotationen jetzt standardisiert sind.
@Componentpublic class ExampleService { public String getName() { return "World!"; }}
Assembly comparisonEdit
Die verschiedenen Injektorimplementierungen (Fabriken, Service Locators und Dependency Injection Container) sind in Bezug auf Dependency Injection nicht so unterschiedlich. Was den Unterschied ausmacht, ist, wo sie verwendet werden dürfen., Verschieben Sie Anrufe in eine Factory oder einen Service Locator aus dem Client in main und plötzlich macht main einen ziemlich guten Dependency Injection Container.
Durch die Verlagerung des gesamten Wissens über den Injektor bleibt ein sauberer Kunde, der frei von Wissen über die Außenwelt ist, zurück. Jedes Objekt, das andere Objekte verwendet, kann jedoch als Client betrachtet werden. Das Objekt, das main enthält, ist keine Ausnahme. Dieses Hauptobjekt verwendet keine Abhängigkeitsinjektion. Es verwendet tatsächlich das Service Locator-Muster. Dies kann nicht vermieden werden, da die Wahl der Dienstimplementierungen irgendwo getroffen werden muss.,
Das Externalisieren der Abhängigkeiten in Konfigurationsdateien ändert nichts an dieser Tatsache. Was diese Realität zu einem guten Design macht, ist, dass der Service Locator nicht über die gesamte Codebasis verteilt ist. Es ist auf einen Ort pro Anwendung beschränkt. Dadurch kann der Rest der Codebasis die Abhängigkeitsinjektion verwenden, um saubere Clients zu erstellen.
Dependency Injection PatternEdit
Die bisherigen Beispiele waren zu einfache Beispiele zum Erstellen einer Zeichenfolge., Das Abhängigkeitsinjektionsmuster ist jedoch am nützlichsten, wenn ein Objektdiagramm erstellt wird, in dem Objekte über Nachrichten kommunizieren. Objekte, die in main erstellt wurden, halten für die Lebensdauer des Programms. Das typische Muster besteht darin, das Diagramm zu erstellen und dann eine Methode für ein Objekt aufzurufen, um den Kontrollfluss in das Objektdiagramm zu senden. So wie main der Einstiegspunkt für den statischen Code ist, ist diese eine Methode der Einstiegspunkt für den nicht statischen Code der Anwendungen.,
AngularJS exampleEdit
Im AngularJS-Framework gibt es nur drei Möglichkeiten, wie eine Komponente (Objekt oder Funktion) direkt auf ihre Abhängigkeiten zugreifen kann:
- Die Komponente kann die Abhängigkeit erstellen, normalerweise mit dem Operator .
- Die Komponente kann die Abhängigkeit nachschlagen, indem sie auf eine globale Variable verweist.
- Der Komponente kann die Abhängigkeit dort übergeben werden, wo sie benötigt wird.
Die ersten beiden Optionen zum Erstellen oder Nachschlagen von Abhängigkeiten sind nicht optimal, da sie die Abhängigkeit von der Komponente fest codieren., Dies macht es schwierig, wenn nicht unmöglich, die Abhängigkeiten zu ändern. Dies ist besonders problematisch in Tests, wo es oft wünschenswert ist, Scheinabhängigkeiten für die Testisolierung bereitzustellen.
Die dritte Option ist die praktikabelste, da sie die Verantwortung für das Auffinden der Abhängigkeit von der Komponente beseitigt. Die Abhängigkeit wird einfach an die Komponente übergeben.
Im obigen Beispiel geht es SomeClass
nicht darum, die Greeter-Abhängigkeit zu erstellen oder zu lokalisieren, sondern einfach um den Greeter, wenn er instanziiert wird.,
Dies ist wünschenswert, aber es liegt in der Verantwortung, die Abhängigkeit von dem Code zu erhalten, der SomeClass
.
Um die Verantwortung für die Abhängigkeitserstellung zu verwalten, verfügt jede AngularJS-Anwendung über einen Injektor. Der Injektor ist ein Service Locator, der für den Aufbau und die Suche von Abhängigkeiten verantwortlich ist.
Hier ist ein Beispiel für die Verwendung des Injektordienstes:
Erstellen Sie einen neuen Injektor, der Komponenten bereitstellen kann, die im Modul myModule
definiert sind, und fordern Sie unseren Greeter-Dienst vom Injektor an., (Dies wird normalerweise automatisch vom AngularJS-Bootstrap ausgeführt.)
var injector = angular.injector();var greeter = injector.get('greeter');
Die Abfrage von Abhängigkeiten löst das Problem der harten Codierung, bedeutet jedoch auch, dass der Injektor in der gesamten Anwendung übergeben werden muss. Das Bestehen des Injektors bricht das Gesetz von Demeter., Um dies zu beheben, verwenden wir eine deklarative Notation in unseren HTML-Vorlagen, um die Verantwortung für die Erstellung von Komponenten an den Injektor zu übergeben, wie in diesem Beispiel:
<div ng-controller="MyController"> <button ng-click="sayHello()">Hello</button></div>
function MyController($scope, greeter) { $scope.sayHello = function() { greeter.greet('Hello World'); };}
Wenn AngularJS den HTML-Code kompiliert, verarbeitet er die Direktive ng-controller
, die den Injektor auffordert, erstellen Sie eine Instanz des Controllers und seiner Abhängigkeiten.
injector.instantiate(MyController);
Dies alles geschieht hinter den Kulissen., Da die ng-controller
auf den Injektor verschoben wird, um die Klasse zu instanziieren, kann sie alle Abhängigkeiten von MyController
erfüllen, ohne dass der Controller jemals etwas über den Injektor weiß. Der Anwendungscode deklariert einfach die benötigten Abhängigkeiten, ohne sich mit dem Injektor befassen zu müssen. Dieses Setup bricht nicht das Gesetz von Demeter.
C#Bearbeiten
Beispiel für die Konstruktorinjektion, Setterinjektion und Schnittstelleninjektion auf C#