Es wurden bereits einige Fragen mit spezifischen Fragen zur Abhängigkeitsinjektion gepostet, z. B. wann sie verwendet werden sollen und welche Rahmenbedingungen dafür zur Verfügung stehen. Jedoch,
Was ist Abhängigkeitsinjektion und wann/warum sollte oder sollte es nicht verwendet werden?
Anstatt dass Ihre Objekte eine Abhängigkeit erzeugen oder ein Factory-Objekt dazu auffordert, eine für sie zu erstellen, übergeben Sie die erforderlichen Abhängigkeiten extern an das Objekt und machen es zu einem Problem eines anderen. Dieser "Jemand" ist entweder ein Objekt weiter oben im Abhängigkeitsgraphen oder ein Abhängigkeitsinjektor (Framework), der den Abhängigkeitsgraphen aufbaut. Eine Abhängigkeit, wie ich sie hier verwende, ist jedes andere Objekt, auf das das aktuelle Objekt einen Verweis halten muss.
Einer der Hauptvorteile der Abhängigkeitseinspritzung besteht darin, dass sie das Testen viel einfacher machen kann. Angenommen, Sie haben ein Objekt, das in seinem Konstruktor Folgendes ausführt:
public SomeClass() {
myObject = Factory.getObject();
}
Dies kann mühsam sein, wenn Sie lediglich einige Komponententests mit SomeClass
ausführen möchten, insbesondere wenn myObject
für komplexe Festplatten oder Netzwerkzugriffe zuständig ist. Sie sehen also jetzt nur myObject
, aber auch irgendwie den Werksruf abfangen. Schwer. Übergeben Sie das Objekt stattdessen als Argument an den Konstruktor. Jetzt haben Sie das Problem an einen anderen Ort verschoben, aber das Testen kann viel einfacher werden. Mach einfach ein Dummy myObject
und gib das weiter. Der Konstruktor würde jetzt ein bisschen wie folgt aussehen:
public SomeClass (MyClass myObject) {
this.myObject = myObject;
}
Dies ist eine Art der Abhängigkeitseingabe - über den Konstruktor. Es sind mehrere Mechanismen möglich.
Wenn Sie keine Abhängigkeitseinspritzung verwenden (wie z. B. in Klassen, die in ihren Konstruktoren zu viel Arbeit leisten usw.), wird es oft schwieriger, Komponenten beim Komponententest zu isolieren.
Als ich 2013 diese Antwort schrieb, war dies ein Hauptthema im Google Testing Blog . Dies ist für mich nach wie vor der größte Vorteil, da Sie möglicherweise nicht immer die zusätzliche Flexibilität in Ihrem Laufzeitdesign benötigen (z. B. für Service Locator oder ähnliche Muster), aber Sie müssen Ihre Klassen häufig während des Testens isolieren können.
Die beste Definition, die ich bisher gefunden habe, ist eine von James Shore :
"Abhängigkeitsinjektion" ist ein 25-Dollar-Betrag Laufzeit für ein 5-Cent-Konzept. [...] Abhängigkeitsinjektion bedeutet ein Objekt seine Instanzvariablen. [...].
Es gibt einen Artikel von Martin Fowler , der sich auch als nützlich erweisen kann.
Bei der Abhängigkeitsinjektion werden im Wesentlichen die Objekte bereitgestellt, die ein Objekt benötigt (seine Abhängigkeiten), anstatt sie selbst erstellen zu lassen. Dies ist eine sehr nützliche Testmethode, da Abhängigkeiten überspottet oder ausgeblendet werden können.
Abhängigkeiten können auf viele Arten in Objekte eingefügt werden (z. B. Konstruktorinjektion oder Setterinjektion). Man kann sogar spezialisierte Abhängigkeitsinjektions-Frameworks (z. B. Spring) verwenden, aber dies ist sicherlich nicht erforderlich. Sie benötigen diese Frameworks nicht, um Abhängigkeiten zu erhalten. Das explizite Instantiieren und Übergeben von Objekten (Abhängigkeiten) ist eine ebenso gute Injektion wie eine Injektion durch das Framework.
Ich fand dieses lustige Beispiel in Bezug auf lose Kopplung :
Jede Anwendung besteht aus vielen Objekten, die zusammenarbeiten, um nützliche Funktionen auszuführen. Traditionell ist jedes Objekt dafür verantwortlich, seine eigenen Referenzen auf die abhängigen Objekte (Abhängigkeiten) zu erhalten, mit denen es zusammenarbeitet. Dies führt zu stark gekoppelten Klassen und schwer zu testendem Code.
Betrachten Sie beispielsweise ein Car
-Objekt.
Ein Car
hängt von den Rädern, dem Motor, dem Kraftstoff, der Batterie usw. ab. Traditionell definieren wir die Marke solcher abhängiger Objekte zusammen mit der Definition des Car
-Objekts.
Ohne Abhängigkeitseinspritzung (DI):
class Car{
private Wheel wh = new NepaliRubberWheel();
private Battery bt = new ExcideBattery();
//The rest
}
Hier ist das Car
-Objekt für das Erstellen der abhängigen Objekte verantwortlich.
Was ist, wenn wir den Typ des abhängigen Objekts ändern möchten - sagen Sie Wheel
- nach den anfänglichen NepaliRubberWheel()
-Punktierungen? Wir müssen das Car-Objekt mit seiner neuen Abhängigkeit, sagen wir ChineseRubberWheel()
, neu erstellen. Dies kann jedoch nur der Hersteller Car
tun.
Was macht der Dependency Injection
dann für ...?
Bei der Verwendung der Abhängigkeitseinspritzung erhalten Objekte ihre Abhängigkeiten zur Laufzeit und nicht zur Kompilierzeit (Fahrzeugherstellungszeit) . Damit wir die Wheel
-Datei jetzt jederzeit ändern können. Hier kann dependency
(wheel
) zur Laufzeit in Car
injiziert werden.
Nach der Abhängigkeitsinjektion:
Hier sind wir injizieren die Abhängigkeiten (Rad und Batterie) zur Laufzeit. Daher der Begriff: Abhängigkeitseinspritzung.
class Car{
private Wheel wh = [Inject an Instance of Wheel (dependency of car) at runtime]
private Battery bt = [Inject an Instance of Battery (dependency of car) at runtime]
Car(Wheel wh,Battery bt) {
this.wh = wh;
this.bt = bt;
}
//Or we can have setters
void setWheel(Wheel wh) {
this.wh = wh;
}
}
Quelle: Abhängigkeitsinjektion verstehen
Abhängigkeitsinjektion ist eine Praxis, bei der Objekte so entworfen werden, dass sie Instanzen der Objekte von anderen Codeteilen erhalten, anstatt sie intern zu erstellen. Dies bedeutet, dass jedes Objekt, das die Schnittstelle implementiert, das von dem Objekt benötigt wird, ohne Änderung des Codes ersetzt werden kann, was das Testen vereinfacht und die Entkopplung verbessert.
Betrachten Sie zum Beispiel diese Klassen:
public class PersonService {
public void addManager( Person employee, Person newManager ) { ... }
public void removeManager( Person employee, Person oldManager ) { ... }
public Group getGroupByManager( Person manager ) { ... }
}
public class GroupMembershipService() {
public void addPersonToGroup( Person person, Group group ) { ... }
public void removePersonFromGroup( Person person, Group group ) { ... }
}
In diesem Beispiel würde die Implementierung von PersonService::addManager
und PersonService::removeManager
eine Instanz der GroupMembershipService
benötigen, um ihre Arbeit zu erledigen. Ohne Abhängigkeitsinjektion würde dies herkömmlicherweise die Instanz eines neuen GroupMembershipService
im Konstruktor von PersonService
sein und dieses Instanzattribut in beiden Funktionen verwenden. Wenn der Konstruktor von GroupMembershipService
jedoch mehrere Dinge benötigt oder, was noch schlimmer ist, gibt es einige Initialisierungs-Setter, die für die GroupMembershipService
aufgerufen werden müssen, der Code wächst ziemlich schnell und die PersonService
hängt jetzt nicht nur von der GroupMembershipService
ab, sondern auch alles andere, von dem GroupMembershipService
abhängt. Darüber hinaus ist die Verknüpfung zu GroupMembershipService
in PersonService
hartcodiert. Dies bedeutet, dass Sie eine GroupMembershipService
nicht zu Testzwecken "dummy-up" machen oder ein Strategiemuster in verschiedenen Teilen Ihrer Anwendung verwenden können.
Mit Dependency Injection können Sie die GroupMembershipService
in Ihrer PersonService
nicht instanziieren, sondern entweder an den PersonService
-Konstruktor übergeben oder eine Property (Getter und Setter) hinzufügen, um eine lokale Instanz davon festzulegen. Dies bedeutet, dass sich Ihre PersonService
nicht länger um die Erstellung einer GroupMembershipService
kümmern muss, sie akzeptiert nur die angegebenen Variablen und arbeitet mit ihnen. Dies bedeutet auch, dass alles, was eine Unterklasse von GroupMembershipService
ist oder die GroupMembershipService
-Schnittstelle implementiert, in die PersonService
"injiziert" werden kann und die PersonService
keine Kenntnis von der Änderung haben muss.
Die akzeptierte Antwort ist gut - aber ich möchte hinzufügen, dass DI dem klassischen Vermeiden von fest codierten Konstanten im Code sehr ähnlich ist.
Wenn Sie eine Konstante wie einen Datenbanknamen verwenden, verschieben Sie sie schnell aus dem Inneren des Codes in eine Konfigurationsdatei und übergeben eine Variable mit diesem Wert an die Stelle, an der sie benötigt wird. Der Grund dafür ist, dass sich diese Konstanten normalerweise häufiger als der Rest des Codes ändern. Zum Beispiel, wenn Sie den Code in einer Testdatenbank testen möchten.
DI ist in der Welt der objektorientierten Programmierung analog. Die Werte anstelle von konstanten Literalen sind ganze Objekte. Der Grund für das Verschieben des Codes aus dem Klassencode ist jedoch ähnlich - die Objekte ändern sich häufiger als der Code, der sie verwendet. Ein wichtiger Fall, in dem eine solche Änderung erforderlich ist, sind Tests.
Versuchen wir es mit einem einfachen Beispiel mit den Klassen Car und Engine. Jedes Auto benötigt einen Motor, um zumindest irgendwohin zu gehen. Im Folgenden wird beschrieben, wie der Code ohne Abhängigkeitseingabe aussehen wird.
public class Car
{
public Car()
{
GasEngine engine = new GasEngine();
engine.Start();
}
}
public class GasEngine
{
public void Start()
{
Console.WriteLine("I use gas as my fuel!");
}
}
Um die Autoklasse zu instanziieren, verwenden wir den nächsten Code:
Car car = new Car();
Das Problem mit diesem Code, dass wir eng mit GasEngine gekoppelt sind, und wenn wir uns entscheiden, es in ElectricityEngine zu ändern, müssen wir die Car-Klasse neu schreiben. Und je größer die Anwendung, desto mehr Probleme und Kopfschmerzen müssen wir hinzufügen und verwenden, um einen neuen Motortyp zu verwenden.
Mit anderen Worten, bei diesem Ansatz ist unsere High-Level-Klasse Car von der Klasse GasEngine der unteren Ebene abhängig, die gegen das Abhängigkeitsinversionsprinzip (DIP) von SOLID verstößt. DIP legt nahe, dass wir uns auf Abstraktionen verlassen sollten, nicht auf konkrete Klassen. Um dies zu befriedigen, führen wir die IEngine-Schnittstelle ein und schreiben den Code wie folgt um:
public interface IEngine
{
void Start();
}
public class GasEngine : IEngine
{
public void Start()
{
Console.WriteLine("I use gas as my fuel!");
}
}
public class ElectricityEngine : IEngine
{
public void Start()
{
Console.WriteLine("I am electrocar");
}
}
public class Car
{
private readonly IEngine _engine;
public Car(IEngine engine)
{
_engine = engine;
}
public void Run()
{
_engine.Start();
}
}
Jetzt ist unsere Klasse Car nur von der IEngine-Schnittstelle abhängig, nicht von einer bestimmten Implementierung der Engine. Der einzige Trick ist nun, wie wir eine Instanz des Autos erstellen und ihm eine konkrete konkrete Motorklasse wie GasEngine oder ElectricityEngine zuweisen. Hier kommt Abhängigkeitsinjektion ins Spiel.
Car gasCar = new Car(new GasEngine());
gasCar.Run();
Car electroCar = new Car(new ElectricityEngine());
electroCar.Run();
Hier geben wir unsere Abhängigkeit (Engine-Instanz) grundsätzlich an den Car-Konstruktor weiter. Nun haben unsere Klassen eine lose Kopplung zwischen Objekten und deren Abhängigkeiten, und wir können problemlos neue Motortypen hinzufügen, ohne die Klasse Car zu ändern.
Der Hauptvorteil von Dependency Injection besteht darin, dass Klassen lose gekoppelt sind, da sie keine fest codierten Abhängigkeiten haben. Dies folgt dem Prinzip der Inversion der Abhängigkeit, das oben erwähnt wurde. Anstatt auf bestimmte Implementierungen zu verweisen, fordern Klassen Abstraktionen an (normalerweise Interface), die ihnen bei der Erstellung der Klasse zur Verfügung gestellt werden.
Am Ende ist Dependency Injection nur eine Technik für lockere Kopplung zwischen Objekten und ihren Abhängigkeiten . Anstatt die Abhängigkeiten, die die Klasse in .__ benötigt, nicht direkt zu instanziieren. Um seine Aktionen auszuführen, werden der Klasse Abhängigkeiten zur Verfügung gestellt (am häufigsten) über Konstruktorinjektion.
Wenn wir viele Abhängigkeiten haben, ist es sehr empfehlenswert, Inversion of Control (IoC) -Container zu verwenden, um festzustellen, welche Schnittstellen für alle unsere Abhängigkeiten bestimmten Implementierungen zugeordnet werden sollten unser Objekt Zum Beispiel könnten wir im Mapping für den IoC-Container angeben, dass die Abhängigkeit von IEngine der Klasse GasEngine zugeordnet werden soll, und wenn wir den IoC-Container nach einer Instanz unserer Klasse Car fragen erstellt automatisch unsere Car - Klasse mit einer übergebenen GasEngine - Abhängigkeit.
UPDATE: Vor kurzem beobachtete ich den Kurs über EF Core von Julie Lerman und mochte auch ihre kurze Definition über DI.
Abhängigkeitsinjektion ist ein Muster, mit dem Ihre Anwendung Injizieren kann. fliegende Objekte für Klassen, die sie benötigen, ohne diese .__ zu erzwingen. Klassen, die für diese Objekte verantwortlich sind. Damit kann Ihr Code .__ sein. locker gekoppelt, und Entity Framework Core wird in dieselbe System von Dienstleistungen.
Stellen wir uns vor, Sie wollen angeln gehen:
Ohne Abhängigkeitsinjektion müssen Sie sich um alles selbst kümmern. Sie müssen ein Boot finden, eine Angel kaufen, Köder suchen usw. Es ist natürlich möglich, aber es bringt Ihnen viel Verantwortung. In Bezug auf Software bedeutet dies, dass Sie nach all diesen Dingen suchen müssen.
Mit der Abhängigkeitseinspritzung kümmert sich ein anderer um die Vorbereitung und stellt Ihnen die erforderliche Ausrüstung zur Verfügung. Sie werden das Boot, die Angelrute und den Köder erhalten ("injiziert") - alles einsatzbereit.
Dies ist die einfachste Erklärung für Dependency Injection und Dependency Injection Container, die ich je gesehen habe:
Dependency Injection und Dependency Injection Containers sind verschiedene Dinge:
Sie benötigen keinen Container für die Abhängigkeitsinjektion. Ein Container kann Ihnen jedoch helfen.
Bedeutet "Abhängigkeitsinjektion" nicht nur parametrisierte Konstruktoren und öffentliche Setter?
James Shores Artikel zeigt die folgenden Vergleichsbeispiele .
Konstruktor ohne Abhängigkeitsinjektion:
public class Example {
private DatabaseThingie myDatabase;
public Example() {
myDatabase = new DatabaseThingie();
}
public void doStuff() {
...
myDatabase.getData();
...
}
}
Konstruktor mit Abhängigkeitsinjektion:
public class Example {
private DatabaseThingie myDatabase;
public Example(DatabaseThingie useThisDatabaseInstead) {
myDatabase = useThisDatabaseInstead;
}
public void doStuff() {
...
myDatabase.getData();
...
}
}
Was ist Abhängigkeitsinjektion (DI)?
Wie bereits erwähnt, beseitigt Dependency Injection (DI) die Verantwortung für die direkte Erstellung und Verwaltung der Lebensdauer anderer Objektinstanzen, auf die sich unsere Interessenklasse (Verbraucherklasse) bezieht abhängig (im ML-Sinne ). Diese Instanzen werden stattdessen an unsere Consumer-Klasse übergeben, in der Regel als Konstruktorparameter oder über Eigenschaftssetter (die Verwaltung der Instanzierung des Abhängigkeitsobjekts und die Übergabe an die Consumer-Klasse erfolgt normalerweise durch eine Inversion of Control (IoC). Container, aber das ist ein anderes Thema).
DI, DIP und SOLID
Insbesondere im Paradigma von Robert C. Martins SOLID-Prinzipien des objektorientierten Designs ist DI
eine der möglichen Implementierungen des Dependency Inversion Principle (DIP) . Das DIP ist das D
des SOLID
-Mantras - Andere DIP-Implementierungen umfassen den Service Locator und Plugin-Muster.
Ziel des DIP ist es, enge, konkrete Abhängigkeiten zwischen Klassen zu entkoppeln und stattdessen die Kopplung durch eine Abstraktion zu lösen, die je nach Sprache und Ansatz über einen interface
, abstract class
oder pure virtual class
erreicht werden kann.
Ohne das DIP ist unser Code (ich habe diese "konsumierende Klasse" genannt) direkt mit einer konkreten Abhängigkeit verbunden und ist oft auch mit der Verantwortung belastet, zu wissen, wie eine Instanz dieser Abhängigkeit abgerufen und verwaltet wird, d. H. Konzeptionell:
"I need to create/use a Foo and invoke method `GetBar()`"
Während nach Anwendung des DIP die Anforderung gelockert wird und die Sorge, die Lebensdauer der Abhängigkeit Foo
zu erhalten und zu verwalten, beseitigt wurde:
"I need to invoke something which offers `GetBar()`"
Warum DIP (und DI) verwenden?
Das Entkoppeln von Abhängigkeiten zwischen Klassen auf diese Weise ermöglicht ein einfaches Ersetzen dieser Abhängigkeitsklassen durch andere Implementierungen, die auch die Voraussetzungen der Abstraktion erfüllen (z. B. kann die Abhängigkeit durch eine andere Implementierung der umgeschaltet werden) gleiche Schnittstelle). Darüber hinaus besteht, wie andere bereits erwähnt haben, möglicherweise der häufigste Grund für die Entkopplung von Klassen über das DIP darin, dass eine konsumierende Klasse isoliert getestet werden kann, da dieselben Abhängigkeiten nun gestubbt werden können und/oder verspottet.
Eine Konsequenz von DI ist, dass das Lebensdauermanagement von Abhängigkeitsobjektinstanzen nicht mehr von einer konsumierenden Klasse gesteuert wird, da das Abhängigkeitsobjekt jetzt an die konsumierende Klasse übergeben wird (über Konstruktor- oder Setter-Injection).
Dies kann auf verschiedene Arten angezeigt werden:
Create
im Werk abrufen und diese Instanzen nach Abschluss entsorgen.Wann wird DI verwendet?
MyDepClass
threadsicher - was ist, wenn wir es zu einem Singleton machen und allen Konsumenten dieselbe Instanz injizieren?)Beispiel
Hier ist eine einfache C # -Implementierung. In Anbetracht der folgenden Verbraucherklasse:
public class MyLogger
{
public void LogRecord(string somethingToLog)
{
Console.WriteLine("{0:HH:mm:ss} - {1}", DateTime.Now, somethingToLog);
}
}
Obwohl es scheinbar harmlos ist, gibt es zwei static
-Abhängigkeiten von zwei anderen Klassen, System.DateTime
und System.Console
, die nicht nur die Optionen für die Protokollausgabe einschränken (Protokollierung auf der Konsole ist wertlos, wenn niemand zugreift), sondern es ist noch schlimmer, wenn sie automatisch getestet werden die Abhängigkeit von einer nicht deterministischen Systemuhr.
Wir können jedoch DIP
auf diese Klasse anwenden, indem wir das Problem der Zeitstempelung als Abhängigkeit abstrahieren und MyLogger
nur auf eine einfache Schnittstelle koppeln:
public interface IClock
{
DateTime Now { get; }
}
Wir können auch die Abhängigkeit von Console
von einer Abstraktion wie TextWriter
lösen. Die Abhängigkeitsinjektion wird in der Regel als constructor
-Injektion (Übergabe einer Abstraktion an eine Abhängigkeit als Parameter an den Konstruktor einer konsumierenden Klasse) oder Setter Injection
(Übergabe der Abhängigkeit über einen setXyz()
-Setter oder eine .Net-Eigenschaft mit {set;}
definiert). Konstruktorinjektion wird bevorzugt, da dies garantiert, dass die Klasse nach der Erstellung einen korrekten Status aufweist und die internen Abhängigkeitsfelder als readonly
(C #) oder final
(Java) gekennzeichnet werden können. Wenn Sie also die Konstruktorinjektion im obigen Beispiel verwenden, erhalten Sie:
public class MyLogger : ILogger // Others will depend on our logger.
{
private readonly TextWriter _output;
private readonly IClock _clock;
// Dependencies are injected through the constructor
public MyLogger(TextWriter stream, IClock clock)
{
_output = stream;
_clock = clock;
}
public void LogRecord(string somethingToLog)
{
// We can now use our dependencies through the abstraction
// and without knowledge of the lifespans of the dependencies
_output.Write("{0:yyyy-MM-dd HH:mm:ss} - {1}", _clock.Now, somethingToLog);
}
}
(Es muss ein konkretes Clock
bereitgestellt werden, das natürlich auf DateTime.Now
zurückgesetzt werden kann, und die beiden Abhängigkeiten müssen von einem IoC-Container per Konstruktorinjektion bereitgestellt werden.)
Es kann ein automatisierter Unit Test erstellt werden, der definitiv beweist, dass unser Logger korrekt funktioniert, da wir nun die Kontrolle über die Abhängigkeiten haben - die Zeit - und wir können die geschriebene Ausgabe ausspionieren:
[Test]
public void LoggingMustRecordAllInformationAndStampTheTime()
{
// Arrange
var mockClock = new Mock<IClock>();
mockClock.Setup(c => c.Now).Returns(new DateTime(2015, 4, 11, 12, 31, 45));
var fakeConsole = new StringWriter();
// Act
new MyLogger(fakeConsole, mockClock.Object)
.LogRecord("Foo");
// Assert
Assert.AreEqual("2015-04-11 12:31:45 - Foo", fakeConsole.ToString());
}
Nächste Schritte
Die Abhängigkeitsinjektion ist immer mit einem Inversion of Control-Container (IoC) verknüpft, um die konkreten Abhängigkeitsinstanzen zu injizieren (bereitzustellen) und die Lebensdauerinstanzen zu verwalten. Während des Konfigurations-/Bootstrapping-Vorgangs können in IoC
-Containern folgende Elemente definiert werden:
IBar
-Instanz anfordert, wird eine ConcreteBar
-Instanz zurückgegeben" )IDisposable
vertraut und übernehmen die Verantwortung für Disposing
-Abhängigkeiten entsprechend der konfigurierten Lebensdauerverwaltung.Sobald IoC-Container konfiguriert/gebootet wurden, arbeiten sie in der Regel nahtlos im Hintergrund, sodass sich der Codierer auf den jeweiligen Code konzentrieren kann, ohne sich um Abhängigkeiten kümmern zu müssen.
Der Schlüssel zu DI-freundlichem Code besteht darin, die statische Kopplung von Klassen zu vermeiden und new () nicht zum Erstellen von Abhängigkeiten zu verwenden
Wie im obigen Beispiel erfordert das Entkoppeln von Abhängigkeiten einen gewissen Entwicklungsaufwand, und für den Entwickler ist ein Paradigmenwechsel erforderlich, um die Gewohnheit aufzuheben, Abhängigkeiten new
direkt zu behandeln und stattdessen dem Container zu vertrauen, um Abhängigkeiten zu verwalten.
Aber es gibt viele Vorteile, insbesondere in Bezug auf die Möglichkeit, Ihre Interessengruppe gründlich zu testen.
Hinweis : Die Erstellung/Zuordnung/Projektion (über new ..()
) von POCO/POJO/Serialization DTOs/Entity Graphs/Anonymous JSON-Projektionen et al. - dh "Nur Daten" -Klassen oder Datensätze -, die von Methoden verwendet oder zurückgegeben werden, werden nicht als Abhängigkeiten (im UML-Sinne) betrachtet und unterliegen nicht DI. Es ist in Ordnung, new
zu verwenden, um diese zu projizieren.
Konzept der Abhängigkeitsinjektion verständlich machen. Nehmen wir ein Beispiel für den Schalterknopf, um eine Glühlampe ein- oder auszuschalten.
Der Switch muss vorher wissen, an welche Glühlampe ich angeschlossen bin (hartcodierte Abhängigkeit). So,
Schalter -> PermanentBulb / Schalter ist direkt an eine permanente Glühlampe angeschlossen; Testung nicht problemlos möglich
/ -
Switch(){
PermanentBulb = new Bulb();
PermanentBulb.Toggle();
}
Switch weiß nur, dass ich die Bulb, die mir übergeben wird, ein-/ausschalten muss. So,
Switch -> Bulb1 OR Bulb2 OR NightBulb (injizierte Abhängigkeit)
Switch(AnyBulb){ //pass it whichever bulb you like
AnyBulb.Toggle();
}
James Beispiel für Schalter und Glühlampe ändern:
public class SwitchTest {
TestToggleBulb() {
MockBulb mockbulb = new MockBulb();
// MockBulb is a subclass of Bulb, so we can
// "inject" it here:
Switch switch = new Switch(mockBulb);
switch.ToggleBulb();
mockBulb.AssertToggleWasCalled();
}
}
public class Switch {
private Bulb myBulb;
public Switch() {
myBulb = new Bulb();
}
public Switch(Bulb useThisBulbInstead) {
myBulb = useThisBulbInstead;
}
public void ToggleBulb() {
...
myBulb.Toggle();
...
}
}`
Der Sinn von Dependency Injection (DI) ist es, den Quellcode der Anwendung clean und stabil zu halten :
In der Praxis trennt jedes Entwurfsmuster die Bedenken, damit zukünftige Änderungen die Mindestanzahl von Dateien beeinflussen.
Die spezifische Domäne von DI ist die Delegierung der Abhängigkeitskonfiguration und -initialisierung.
Wenn Sie gelegentlich außerhalb von Java arbeiten, denken Sie daran, wie source
häufig in vielen Skriptsprachen verwendet wird (Shell, Tcl usw. oder sogar import
in Python, die zu diesem Zweck verwendet werden).
Betrachten Sie das einfache dependent.sh
-Skript:
#!/bin/sh
# Dependent
touch "one.txt" "two.txt"
archive_files "one.txt" "two.txt"
Das Skript ist abhängig: Es wird nicht erfolgreich alleine ausgeführt (archive_files
ist nicht definiert).
Sie definieren archive_files
im archive_files_Zip.sh
-Implementierungsskript (in diesem Fall mit Zip
):
#!/bin/sh
# Dependency
function archive_files {
Zip files.Zip "[email protected]"
}
Anstatt source
- Implementierungsskript direkt im abhängigen Skript zu verwenden, verwenden Sie einen injector.sh
"Container", der beide "Komponenten" umschließt:
#!/bin/sh
# Injector
source ./archive_files_Zip.sh
source ./dependent.sh
Der archive_files
Abhängigkeit wurde gerade injiziert in abhängig Skript.
Sie könnten eine Abhängigkeit eingefügt haben, die archive_files
mithilfe von tar
oder xz
implementiert.
Wenn dependent.sh
-Skript Abhängigkeiten direkt verwendet, heißt der Ansatz Abhängigkeitsabfrage (was sich entgegengesetzt zu Abhängigkeitseinspritzung handelt:
#!/bin/sh
# Dependent
# dependency look-up
source ./archive_files_Zip.sh
touch "one.txt" "two.txt"
archive_files "one.txt" "two.txt"
Nun besteht das Problem darin, dass die abhängige "Komponente" die Initialisierung selbst durchführen muss.
Der Quellcode der "Komponente" ist nicht clean noch stable , da für jede Änderung der Initialisierung von Abhängigkeiten ein neues Release für die Quellcodedatei von "Komponenten" erforderlich ist.
DI wird nicht so stark betont und popularisiert wie in Java-Frameworks.
Es ist jedoch ein generischer Ansatz, um die Bedenken zu trennen:
Die Verwendung der Konfiguration nur mit Abhängigkeitsabfrage hilft nicht, da sich die Anzahl der Konfigurationsparameter je Abhängigkeit (z. B. neuer Authentifizierungstyp) sowie die Anzahl der unterstützten Arten von Abhängigkeiten (z. B. neuer Datenbanktyp) ändern kann.
Alle obigen Antworten sind gut, mein Ziel ist es, das Konzept auf einfache Weise zu erklären, so dass jeder ohne Programmierkenntnisse auch das Konzept verstehen kann
Die Abhängigkeitsinjektion ist eines der Entwurfsmuster, die uns helfen, komplexe Systeme auf einfachere Weise zu erstellen.
Wir können eine Vielzahl von Anwendungen dieses Musters in unserem täglichen Leben beobachten. Einige der Beispiele sind Kassettenrekorder, VCD, CD-Laufwerk usw.
Das obige Bild zeigt ein tragbares Tonbandgerät von Band zu Band, Mitte des 20. Jahrhunderts. Quelle .
Die Hauptabsicht eines Tonbandgeräts besteht darin, Ton aufzunehmen oder wiederzugeben.
Beim Entwerfen eines Systems ist eine Rolle erforderlich, um Ton oder Musik aufzunehmen oder wiederzugeben. Es gibt zwei Möglichkeiten, dieses System zu entwerfen
Wenn wir die erste verwenden, müssen wir die Maschine öffnen, um die Rolle zu wechseln. Wenn wir uns für die zweite Variante entscheiden, bei der ein Haken für die Rolle platziert wird, erhalten wir einen zusätzlichen Vorteil, wenn wir Musik abspielen, indem wir die Rolle wechseln. und auch die Funktion zu reduzieren, nur um zu spielen, was auch immer in der Rolle ist.
Ebenso wie bei der Abhängigkeitsinjektion werden die Abhängigkeiten ausgelagert, um sich nur auf die spezifische Funktionalität der Komponente zu konzentrieren, sodass unabhängige Komponenten zu einem komplexen System verbunden werden können.
Die Hauptvorteile, die wir mit der Abhängigkeitsinjektion erzielt haben.
Heute bildet dieses Konzept die Grundlage bekannter Frameworks in der Programmierwelt. The Spring Angular etc sind die bekannten Software-Frameworks, die auf diesem Konzept aufbauen
Die Abhängigkeitsinjektion ist ein Muster, das zum Erstellen von Instanzen von Objekten verwendet wird, auf die sich andere Objekte stützen, ohne beim Kompilieren zu wissen, welche Klasse verwendet wird, um diese Funktionalität bereitzustellen, oder einfach, wie Eigenschaften in ein Objekt injiziert werden.
Beispiel für die Abhängigkeitsinjektion
Bisher haben wir Code wie diesen geschrieben
Public MyClass{
DependentClass dependentObject
/*
At somewhere in our code we need to instantiate
the object with new operator inorder to use it or perform some method.
*/
dependentObject= new DependentClass();
dependentObject.someMethod();
}
Mit der Abhängigkeitsinjektion entfernt der Abhängigkeitsinjektor die Instanziierung für uns
Public MyClass{
/* Dependency injector will instantiate object*/
DependentClass dependentObject
/*
At somewhere in our code we perform some method.
The process of instantiation will be handled by the dependency injector
*/
dependentObject.someMethod();
}
Sie können auch lesen
nterschied zwischen Inversion der Kontrolle und Abhängigkeitsinjektion
Dependency Injection (DI) bedeutet, die voneinander abhängigen Objekte zu entkoppeln. Angenommen, Objekt A ist von Objekt B abhängig, so dass die Idee besteht, diese Objekte voneinander zu entkoppeln. Wir müssen das Objekt nicht hart codieren, sondern müssen trotz der Kompilierzeit Abhängigkeiten zu Objekten zur Laufzeit freigeben. Wenn wir darüber sprechen
Wir müssen das Objekt nicht mit einem neuen Schlüsselwort hart codieren, sondern die Bean-Abhängigkeit in der Konfigurationsdatei definieren. Der Federbehälter ist für das Anschließen aller verantwortlich.
IOC ist ein allgemeines Konzept und kann auf viele verschiedene Arten ausgedrückt werden. Abhängigkeitseinspritzung ist ein konkretes Beispiel für IOC.
Konstruktor-basierte DI wird ausgeführt, wenn der Container einen Klassenkonstruktor mit einer Reihe von Argumenten aufruft, die jeweils eine Abhängigkeit von einer anderen Klasse darstellen.
public class Triangle {
private String type;
public String getType(){
return type;
}
public Triangle(String type){ //constructor injection
this.type=type;
}
}
<bean id=triangle" class ="com.test.dependencyInjection.Triangle">
<constructor-arg value="20"/>
</bean>
Setter-based DI wird durch den Container erreicht, der Setter-Methoden für Ihre Beans aufruft, nachdem ein Konstruktor ohne Argumente oder eine statische Factory-Methode ohne Argument aufgerufen wurde, um Ihr Bean zu instantiieren.
public class Triangle{
private String type;
public String getType(){
return type;
}
public void setType(String type){ //setter injection
this.type = type;
}
}
<!-- setter injection -->
<bean id="triangle" class="com.test.dependencyInjection.Triangle">
<property name="type" value="equivialteral"/>
HINWEIS: Es ist eine gute Faustregel, Konstruktorargumente für zwingende Abhängigkeiten und Setter für optionale Abhängigkeiten zu verwenden. Beachten Sie, dass die Annotation, die als @Required-Annotation für einen Setter verwendet wird, verwendet werden kann, um Setter als erforderliche Abhängigkeiten zu erstellen.
Die beste Analogie, die ich mir vorstellen kann, ist der Chirurg und seine Assistenten in einem Operationssaal, in dem der Chirurg die Hauptperson ist, und sein Assistent, der die verschiedenen chirurgischen Komponenten zur Verfügung stellt, wenn er sie benötigt, damit der Chirurg sich auf den einen konzentrieren kann was er am besten kann (Operation). Ohne den Assistenten muss der Chirurg die Komponenten jedes Mal selbst holen, wenn er eines benötigt.
DI, kurz DI, ist eine Technik, um eine gemeinsame zusätzliche Verantwortung (Belastung) für Komponenten zu beseitigen, um die abhängigen Komponenten abzurufen, indem sie ihnen zur Verfügung gestellt werden.
DI bringt Sie dem Single-Responsibility-Prinzip (SR) wie dem surgeon who can concentrate on surgery
näher.
Wann sollte DI verwendet werden? Ich würde die Verwendung von DI in fast allen Produktionsprojekten (klein/groß) empfehlen, insbesondere in sich ständig ändernden Geschäftsumgebungen :)
Warum: Weil Sie möchten, dass Ihr Code einfach testbar ist, verspottet usw., damit Sie Ihre Änderungen schnell testen und auf den Markt bringen können. Abgesehen davon, warum sollten Sie es nicht tun, wenn Sie viele tolle kostenlose Tools/Frameworks zur Verfügung stellen, die Sie auf Ihrem Weg zu einer Codebasis unterstützen, in der Sie mehr Kontrolle haben.
Das bedeutet, dass Objekte nur so viele Abhängigkeiten haben sollten, wie sie für ihre Arbeit benötigen, und dass die Abhängigkeiten gering sein sollten. Des Weiteren sollten die Abhängigkeiten eines Objekts auf Schnittstellen und nicht auf "konkrete" Objekte sein, wenn dies möglich ist. (Ein konkretes Objekt ist ein Objekt, das mit dem Schlüsselwort new erstellt wurde.) Eine lose Kopplung fördert die Wiederverwendbarkeit, die Wartungsfreundlichkeit und die einfache Bereitstellung von „Scheinobjekten“ anstelle von teuren Services.
Die "Dependency Injection" (DI) wird auch als "Inversion of Control" (IoC) bezeichnet und kann als Technik zur Förderung dieser lockeren Kopplung verwendet werden.
Es gibt zwei Hauptansätze zur Implementierung von DI:
Hierbei handelt es sich um die Technik, Objekte Abhängigkeiten an den Konstruktor zu übergeben.
Beachten Sie, dass der Konstruktor eine Schnittstelle und kein konkretes Objekt akzeptiert. Beachten Sie außerdem, dass eine Ausnahme ausgelöst wird, wenn der Parameter orderDao den Wert null hat. Dies unterstreicht die Wichtigkeit, eine gültige Abhängigkeit zu erhalten. Constructor Injection ist meiner Meinung nach der bevorzugte Mechanismus, um einem Objekt seine Abhängigkeiten zu geben. Dem Entwickler ist beim Aufrufen des Objekts klar, welche Abhängigkeiten dem Objekt "Person" für eine ordnungsgemäße Ausführung zugewiesen werden müssen.
Beachten Sie jedoch folgendes Beispiel: Angenommen, Sie haben eine Klasse mit zehn Methoden, die keine Abhängigkeiten haben, aber Sie fügen eine neue Methode hinzu, die von IDAO abhängig ist. Sie können den Konstruktor so ändern, dass er Konstruktorinjektion verwendet. Dies kann Sie jedoch dazu zwingen, Änderungen an allen Konstruktoraufrufen überall vorzunehmen. Alternativ können Sie einfach einen neuen Konstruktor hinzufügen, der die Abhängigkeit übernimmt, aber dann weiß ein Entwickler leicht, wann er einen Konstruktor über dem anderen verwenden soll. Wenn die Abhängigkeit sehr teuer ist, warum sollte sie erstellt und an den Konstruktor übergeben werden, wenn sie nur selten verwendet wird? "Setter Injection" ist eine weitere DI-Technik, die in solchen Situationen eingesetzt werden kann.
Setter Injection erzwingt keine Weitergabe von Abhängigkeiten an den Konstruktor. Stattdessen werden die Abhängigkeiten auf öffentliche Eigenschaften festgelegt, die von dem betreffenden Objekt verfügbar gemacht werden. Wie zuvor angedeutet, sind die Hauptmotivatoren dafür:
Hier ist das Beispiel, wie der obige Code aussehen würde:
public class Person {
public Person() {}
public IDAO Address {
set { addressdao = value; }
get {
if (addressdao == null)
throw new MemberAccessException("addressdao" +
" has not been initialized");
return addressdao;
}
}
public Address GetAddress() {
// ... code that uses the addressdao object
// to fetch address details from the datasource ...
}
// Should not be called directly;
// use the public property instead
private IDAO addressdao;
Wir haben beispielsweise 2 Klassen Client
und Service
. Client
verwendet Service
public class Service {
public void doSomeThingInService() {
// ...
}
}
Weg 1)
public class Client {
public void doSomeThingInClient() {
Service service = new Service();
service.doSomeThingInService();
}
}
Weg 2)
public class Client {
Service service = new Service();
public void doSomeThingInClient() {
service.doSomeThingInService();
}
}
Weg 3)
public class Client {
Service service;
public Client() {
service = new Service();
}
public void doSomeThingInClient() {
service.doSomeThingInService();
}
}
1) 2) 3) Verwendung von
Client client = new Client();
client.doSomeThingInService();
Vorteile
Nachteile
Client
KlasseService
Konstruktor ändern, müssen wir den Code an allen Stellen des Service
Objektes ändernMöglichkeit 1) Konstruktorinjektion
public class Client {
Service service;
Client(Service service) {
this.service = service;
}
// Example Client has 2 dependency
// Client(Service service, IDatabas database) {
// this.service = service;
// this.database = database;
// }
public void doSomeThingInClient() {
service.doSomeThingInService();
}
}
Verwenden
Client client = new Client(new Service());
// Client client = new Client(new Service(), new SqliteDatabase());
client.doSomeThingInClient();
Weg 2) Setterinjektion
public class Client {
Service service;
public void setService(Service service) {
this.service = service;
}
public void doSomeThingInClient() {
service.doSomeThingInService();
}
}
Verwenden
Client client = new Client();
client.setService(new Service());
client.doSomeThingInClient();
Weg 3) Schnittstelleninjektion
Überprüfen Sie https://en.wikipedia.org/wiki/Dependency_injection
===
Nun folgt dieser Code Dependency Injection
und ist einfacher für die test Client
Klasse.
Allerdings verwenden wir new Service()
immer noch oft und es ist nicht gut, wenn Sie den Service
Konstruktor ändern. Um dies zu verhindern, können wir DI-Injektor gerne verwenden
1) Einfaches Handbuch Injector
public class Injector {
public static Service provideService(){
return new Service();
}
public static IDatabase provideDatatBase(){
return new SqliteDatabase();
}
public static ObjectA provideObjectA(){
return new ObjectA(provideService(...));
}
}
Verwenden von
Service service = Injector.provideService();
2) Verwenden Sie die Bibliothek: Für Android dagger2
Vorteile
Service
ändern, müssen Sie es nur in der Injector-Klasse ändernConstructor Injection
verwenden, wenn Sie den Konstruktor von Client
betrachten, sehen Sie, wie viele Abhängigkeiten von der Client
Klasse bestehenNachteile
Constructor Injection
verwenden, wird das Service
Objekt erstellt, wenn Client
erstellt wird. Manchmal werden Funktionen in der Client
Klasse verwendet, ohne dass Service
so erstellt wird Service
https://en.wikipedia.org/wiki/Dependency_injection
Eine Abhängigkeit ist ein Objekt, das verwendet werden kann (
Service
)
Eine Injektion ist die Übergabe einer Abhängigkeit (Service
) an ein abhängiges Objekt (Client
), das sie verwenden würde
Ich denke, da jeder für DI geschrieben hat, lassen Sie mich ein paar Fragen stellen.
Dies basiert auf der Antwort @Adam N gepostet.
Warum muss sich PersonService nicht mehr um GroupMembershipService kümmern? Sie haben gerade erwähnt, dass GroupMembership mehrere Dinge (Objekte/Eigenschaften) hat, von denen es abhängt. Wenn GMService in PService erforderlich ist, haben Sie es als Eigenschaft. Sie können das ausspielen, unabhängig davon, ob Sie es injiziert haben oder nicht. Ich möchte nur, dass es injiziert wird, wenn GMService spezifischere untergeordnete Klassen hätte, die Sie bis zur Laufzeit nicht kennen würden. Dann möchten Sie die Unterklasse einfügen. Oder wenn Sie das als Singleton oder Prototyp verwenden wollten. Um ehrlich zu sein, enthält die Konfigurationsdatei alles hartcodiert, bis zu welcher Unterklasse für einen Typ (Schnittstelle) sie während der Kompilierzeit eingefügt werden soll.
EDIT
Ein schöner Kommentar von Jose Maria Arranz zu DI
DI erhöht die Kohäsion, indem die Notwendigkeit, die Richtung der Abhängigkeit zu bestimmen und jeglichen Glue-Code zu schreiben, entfällt.
Falsch. Die Richtung der Abhängigkeiten ist in XML-Form oder als Annotation. Ihre Abhängigkeiten werden als XML-Code und Annotationen geschrieben. XML und Anmerkungen sind Quellcode.
DI reduziert die Kopplung, indem alle Ihre Komponenten modular aufgebaut werden (d. H. Austauschbar sind) und über gut definierte Schnittstellen zueinander verfügen.
Falsch. Sie benötigen kein DI-Framework, um einen auf Schnittstellen basierenden modularen Code zu erstellen.
Über austauschbar: Mit einem sehr einfachen .properties-Archiv und Class.forName können Sie festlegen, welche Klassen geändert werden können. Wenn JEDE Klasse Ihres Codes geändert werden kann, ist Java nicht für Sie. Verwenden Sie eine Skriptsprache. Übrigens: Anmerkungen können nicht ohne Neukompilierung geändert werden.
Meiner Meinung nach gibt es einen einzigen Grund für DI-Gerüste: die Reduzierung der Kesselplatte. Mit einem durchdachten Factory-System können Sie das gleiche, besser kontrollierte und vorhersehbare System wie Ihr bevorzugtes DI-Framework ausführen. DI-Frameworks versprechen Code-Reduktion (XML und Annotationen sind ebenfalls Quellcode). Das Problem ist, dass diese Boiler-Plattenreduzierung in sehr einfachen Fällen nur real ist (eine Instanz pro Klasse und ähnliches). Manchmal ist die Auswahl des entsprechenden Service-Objekts in der realen Welt nicht so einfach wie das Zuordnen einer Klasse zu einem Einzelobjekt.
Dependency Injection bedeutet Abhängigkeit (eigentlich any-way) für einen Codeteil (z. B. eine Klasse), um auf Abhängigkeiten (andere Codeteile, z. B. andere Klassen, es hängt davon ab) in einem modulare Art und Weise, ohne dass sie hartcodiert sind (damit sie sich ändern oder überschreiben können oder nach Bedarf auch zu einem anderen Zeitpunkt geladen werden können)
(und ps, ja, es wurde zu einem übermäßig gehypten 25 $ -Namen für ein ziemlich einfaches Konzept), mein .25
-Cent
Ich weiß, dass es bereits viele Antworten gibt, aber ich fand das sehr hilfreich: http://tutorials.jenkov.com/dependency-injection/index.html
Keine Abhängigkeit:
public class MyDao {
protected DataSource dataSource =
new DataSourceImpl("driver", "url", "user", "password");
//data access methods...
public Person readPerson(int primaryKey) {...}
}
Abhängigkeit:
public class MyDao {
protected DataSource dataSource = null;
public MyDao(String driver, String url, String user, String
password){
this.dataSource = new DataSourceImpl(driver, url, user, password);
}
//data access methods...
public Person readPerson(int primaryKey)
{...}
}
Beachten Sie, wie die Instantiierung DataSourceImpl
in einen Konstruktor verschoben wird. Der Konstruktor benötigt vier Parameter, dh die vier Werte, die von DataSourceImpl
benötigt werden. Obwohl die MyDao
-Klasse immer noch von diesen vier Werten abhängt, erfüllt sie diese Abhängigkeiten selbst nicht mehr. Sie werden von der Klasse bereitgestellt, die eine MyDao
-Instanz erstellt.
Die häufigsten Antworten sind wenig hilfreich, da sie die Abhängigkeitseinspritzung auf eine Weise definieren, die nicht nützlich ist. Wir stimmen darin überein, dass wir mit "Abhängigkeit" ein vorbestehendes anderes Objekt meinen, das unser Objekt X benötigt. Aber wir sagen nicht, dass wir "Abhängigkeitsinjektion" machen, wenn wir sagen
$foo = Foo->new($bar);
Wir rufen einfach die Übergabeparameter in den Konstruktor auf. Wir machen das seit der Erfindung der Konstrukteure regelmäßig.
"Abhängigkeitsinjektion" wird als eine Art "Inversion der Kontrolle" betrachtet, was bedeutet, dass eine bestimmte Logik aus dem Aufrufer genommen wird. Dies ist nicht der Fall, wenn der Aufrufer Parameter übergibt. Wenn DI also DI wäre, würde DI keine Umkehrung der Steuerung bedeuten.
DI bedeutet, dass zwischen dem Aufrufer und dem Konstruktor, der die Abhängigkeiten verwaltet, eine Zwischenebene vorhanden ist. Ein Makefile ist ein einfaches Beispiel für die Abhängigkeitsinjektion. Der "Aufrufer" ist die Person, die in der Befehlszeile "Make-Leiste" eingibt, und der "Konstruktor" ist der Compiler. Das Makefile gibt an, dass die Leiste von foo abhängt, und es führt ein
gcc -c foo.cpp; gcc -c bar.cpp
bevor Sie ein tun
gcc foo.o bar.o -o bar
Die Person, die "Make Bar" eingibt, muss nicht wissen, dass die Bar von Foo abhängt. Die Abhängigkeit wurde zwischen "make bar" und gcc eingefügt.
Der Hauptzweck der Zwischenstufe ist nicht nur die Übergabe der Abhängigkeiten an den Konstruktor, sondern die Auflistung aller Abhängigkeiten in nur eine Stelle - und das Ausblenden vor dem Codierer (nicht durch den Codierer). .
Normalerweise stellt die Zwischenebene Fabriken für die erstellten Objekte bereit, die eine Rolle bereitstellen müssen, die jeder angeforderte Objekttyp erfüllen muss. Das liegt daran, dass Sie durch eine Zwischenstufe, die die Konstruktionsdetails verbirgt, bereits die von den Fabriken verhängte Abstraktionsstrafe erlitten haben. Sie können also auch Fabriken verwenden.
Abhängigkeitseinspritzung ist eine mögliche Lösung für das, was allgemein als "Abhängigkeitsverschleierung" bezeichnet werden könnte. Abhängigkeitsverschleierung ist eine Methode, um die "offensichtliche" Natur aus dem Prozess der Bereitstellung einer Abhängigkeit für eine Klasse herauszuholen, die diese erfordert, und daher in gewisser Weise die Bereitstellung dieser Abhängigkeit für die Klasse zu verschleiern. Das ist nicht unbedingt eine schlechte Sache. Durch Verschleiern der Art und Weise, wie eine Abhängigkeit einer Klasse bereitgestellt wird, ist etwas außerhalb der Klasse für die Erzeugung der Abhängigkeit verantwortlich, was bedeutet, dass in verschiedenen Szenarien eine unterschiedliche Implementierung der Abhängigkeit ohne Änderungen an die Klasse geliefert werden kann zur Klasse. Dies ist ideal für den Wechsel zwischen Produktions- und Testmodus (z. B. Verwendung einer 'Mock'-Dienstabhängigkeit).
Leider ist das Schlimme, dass einige Leute davon ausgegangen sind, dass Sie ein spezielles Framework zur Abhängigkeitsverschleierung benötigen und dass Sie irgendwie ein "kleinerer" Programmierer sind, wenn Sie sich dafür entscheiden, kein bestimmtes Framework zu verwenden. Ein anderer, äußerst verstörender Mythos, von dem viele glauben, ist, dass Abhängigkeitsinjektion der einzige Weg ist, eine Abhängigkeitsverschleierung zu erreichen. Dies ist nachweislich und historisch und offensichtlich zu 100% falsch, aber Sie werden Schwierigkeiten haben, einige Leute davon zu überzeugen, dass es Alternativen zur Abhängigkeitsinjektion für Ihre Abhängigkeitsverschleierungsanforderungen gibt.
Programmierer haben die Abhängigkeitsverschleierungsanforderung seit Jahren verstanden, und viele alternative Lösungen haben sich sowohl vor als auch nach dem Einsetzen der Abhängigkeitsinjektion entwickelt. Es gibt Factory-Muster, aber es gibt auch viele Möglichkeiten, ThreadLocal zu verwenden, bei denen keine Injektion in eine bestimmte Instanz erforderlich ist. Die Abhängigkeit wird effektiv in den Thread eingefügt, was den Vorteil hat, dass das Objekt (über statische Getter-Methoden) für any verfügbar gemacht wird class, die es erfordert, ohne dass Anmerkungen zu den Klassen hinzugefügt werden müssen, die dies erfordern, und Sie müssen komplizierte XML-Elemente einrichten, um dies zu ermöglichen. Wenn Ihre Abhängigkeiten für die Persistenz erforderlich sind (JPA/JDO oder was auch immer), können Sie 'transparente Persistenz' viel einfacher und mit Domänenmodell- und Geschäftsmodellklassen erreichen, die ausschließlich aus POJOs bestehen (d. H. Keine Framework-spezifischen/gesperrten Anmerkungen).
Aus dem Buch ' Fundierter Java-Entwickler: Vitaltechniken von Java 7 und Polyglot-Programmierung
DI ist eine bestimmte Form von IoC, bei der das Finden Ihrer Abhängigkeiten .__ ist. außerhalb der direkten Kontrolle Ihres aktuell ausgeführten Codes.
Dependency Injection (DI) ist eine von Design Patterns, die die grundlegende Funktion von OOP verwendet - die Beziehung zwischen einem Objekt und einem anderen Objekt. Während die Vererbung ein Objekt erbt, um ein komplexeres und spezifischeres anderes Objekt auszuführen, erstellt die Beziehung oder Verknüpfung einfach einen Zeiger auf ein anderes Objekt aus einem Objekt mithilfe des Attributs. Die Leistungsfähigkeit von DI ist in Kombination mit anderen Funktionen von OOP sowie Schnittstellen und verstecktem Code . Angenommen, wir haben einen Kunden (Abonnenten) in der Bibliothek, der sich zur Vereinfachung nur ein Buch ausleihen kann.
Schnittstelle des Buches:
package com.deepam.hidden;
public interface BookInterface {
public BookInterface setHeight(int height);
public BookInterface setPages(int pages);
public int getHeight();
public int getPages();
public String toString();
}
Als nächstes können wir viele Arten von Büchern haben; Eine Art ist Fiktion:
package com.deepam.hidden;
public class FictionBook implements BookInterface {
int height = 0; // height in cm
int pages = 0; // number of pages
/** constructor */
public FictionBook() {
// TODO Auto-generated constructor stub
}
@Override
public FictionBook setHeight(int height) {
this.height = height;
return this;
}
@Override
public FictionBook setPages(int pages) {
this.pages = pages;
return this;
}
@Override
public int getHeight() {
// TODO Auto-generated method stub
return height;
}
@Override
public int getPages() {
// TODO Auto-generated method stub
return pages;
}
@Override
public String toString(){
return ("height: " + height + ", " + "pages: " + pages);
}
}
Jetzt kann der Abonnent dem Buch zugeordnet werden:
package com.deepam.hidden;
import Java.lang.reflect.Constructor;
import Java.lang.reflect.InvocationTargetException;
public class Subscriber {
BookInterface book;
/** constructor*/
public Subscriber() {
// TODO Auto-generated constructor stub
}
// injection I
public void setBook(BookInterface book) {
this.book = book;
}
// injection II
public BookInterface setBook(String bookName) {
try {
Class<?> cl = Class.forName(bookName);
Constructor<?> constructor = cl.getConstructor(); // use it for parameters in constructor
BookInterface book = (BookInterface) constructor.newInstance();
//book = (BookInterface) Class.forName(bookName).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return book;
}
public BookInterface getBook() {
return book;
}
public static void main(String[] args) {
}
}
Alle drei Klassen können für die eigene Implementierung ausgeblendet werden. Jetzt können wir diesen Code für DI verwenden:
package com.deepam.implement;
import com.deepam.hidden.Subscriber;
import com.deepam.hidden.FictionBook;
public class CallHiddenImplBook {
public CallHiddenImplBook() {
// TODO Auto-generated constructor stub
}
public void doIt() {
Subscriber ab = new Subscriber();
// injection I
FictionBook bookI = new FictionBook();
bookI.setHeight(30); // cm
bookI.setPages(250);
ab.setBook(bookI); // inject
System.out.println("injection I " + ab.getBook().toString());
// injection II
FictionBook bookII = ((FictionBook) ab.setBook("com.deepam.hidden.FictionBook")).setHeight(5).setPages(108); // inject and set
System.out.println("injection II " + ab.getBook().toString());
}
public static void main(String[] args) {
CallHiddenImplBook kh = new CallHiddenImplBook();
kh.doIt();
}
}
Es gibt viele verschiedene Möglichkeiten, die Abhängigkeitsinjektion zu verwenden. Es ist möglich, es mit Singleton usw. zu kombinieren, aber im Grunde ist es nur eine Verknüpfung, die durch das Erstellen eines Attributs eines Objekttyps in einem anderen Objekt realisiert wird .. Die Nützlichkeit besteht nur und nur in der Funktion, dem Code, den wir schreiben sollten immer wieder wird für uns immer nach vorne vorbereitet und getan. Deshalb ist DI so eng mit Inversion of Control (IoC) verbunden, was bedeutet, dass unser Programm ein weiteres laufendes Modul übergibt, das Beans in unseren Code injiziert. (Jedes Objekt, das injiziert werden kann, kann signiert oder als Bean betrachtet werden.) Zum Beispiel in Spring wird dies durch Erstellen und Initialisieren von ApplicationContext container ausgeführt, was bei uns funktioniert. Wir erstellen einfach in unserem Code den Context und rufen die Initialisierung der Beans auf. In diesem Moment wurde die Injektion automatisch durchgeführt.
aus Buch Apress.Spring.Persistence.with.Hibernate.Oct.2010
Die Abhängigkeitseinspritzung dient dazu, die Arbeit von .__ zu entkoppeln. Lösung externer Softwarekomponenten aus Ihrem Anwendungsgeschäft logic.Ohne Abhängigkeitsinjektion, die Details, wie eine Komponente Zugriff auf erforderliche Dienste kann mit den Komponenten der Komponente Code. Dies erhöht nicht nur das Fehlerpotenzial, sondern fügt Code .__ hinzu. aufgebläht und vergrößert die Komplexität der Wartung; es verbindet Komponenten enger zusammen, was es schwierig macht, Abhängigkeiten zu ändern, wenn Refactoring oder Testen.
In einfachen Worten ist Abhängigkeitseinspritzung (DI) der Weg, Abhängigkeiten oder enge Kopplung zwischen verschiedenen Objekten zu entfernen. Abhängigkeitseinspritzung gibt jedem Objekt ein zusammenhängendes Verhalten.
DI ist die Implementierung von IOC von Spring, der sagt: "Rufen Sie uns nicht an, wir rufen Sie an". Die Verwendung des Abhängigkeitspritzenprogrammierers muss nicht mit dem neuen Schlüsselwort erstellt werden.
Objekte werden einmal in den Spring-Container geladen und dann wiederverwendet, wenn wir sie benötigen, indem wir diese Objekte mit der getBean (String beanName) -Methode aus dem Spring-Container abrufen.
Abhängigkeitsinjektion (DI) ist Teil der Praxis der Abhängigkeitsinversion (DIP), die auch Inversion of Control (IoC) genannt wird. Grundsätzlich müssen Sie DIP ausführen, da Sie Ihren Code modularer und von Einheiten überprüfbarer machen möchten, anstatt nur ein monolithisches System. Sie beginnen also, Teile des Codes zu identifizieren, die von der Klasse getrennt und entfernt werden können. Nun muss die Implementierung der Abstraktion von außerhalb der Klasse eingefügt werden. Normalerweise kann dies über den Konstruktor erfolgen. Sie erstellen also einen Konstruktor, der die Abstraktion als Parameter akzeptiert, und dies wird als Abhängigkeitsinjektion (über den Konstruktor) bezeichnet. Weitere Informationen zu DIP-, DI- und IoC-Containern finden Sie Here
Abhängigkeitseinspritzung ist das Herzstück des mit Spring Framework verwandten Konzepts. Wenn das Rahmenwerk eines jeden Projektfrühlinges erstellt wird, kann dies eine wichtige Rolle spielen, und hier kommt die Abhängigkeitseinspritzung in Pitcher.
Angenommen, Sie haben in Java zwei verschiedene Klassen als Klasse A und Klasse B erstellt, und was auch immer die Funktion in Klasse B zur Verfügung steht, die Sie in Klasse A verwenden möchten, kann zu diesem Zeitpunkt Abhängigkeitsinjektion verwendet werden, wo dies möglich ist Kistenobjekt einer Klasse in einer anderen Klasse. Auf dieselbe Weise können Sie eine ganze Klasse in eine andere Klasse einfügen, um sie zugänglich zu machen. Durch diese Art und Weise kann die Abhängigkeit überwunden werden.
ABHÄNGIGKEITSINJEKTION IS EINFACH BEIM KLEBEN VON ZWEI KLASSEN UND AT DER GLEICHEN ZEIT, DIE SIE SEPARAT HALTEN.
Ich würde eine etwas andere, kurze und genaue Definition dessen vorschlagen, was Abhängigkeitsinjektion ist, und zwar auf das primäre Ziel und nicht auf die technischen Mittel (im Anschluss an hier ):
Abhängigkeitseinspritzung ist der Vorgang des Erstellens des statischen statuslosen Diagramm von Service-Objekten, bei denen jeder Service durch seine .__ parametrisiert wird. Abhängigkeiten.
Die Objekte, die wir in unseren Anwendungen erstellen (unabhängig davon, ob wir Java, C # oder eine andere objektorientierte Sprache verwenden) fallen normalerweise in eine von zwei Kategorien: stateless, statische und globale "Service-Objekte" (Module) sowie Stateful, Dynamic und Local "Datenobjekte".
Das Moduldiagramm - das Diagramm der Serviceobjekte - wird normalerweise beim Start der Anwendung erstellt. Dies kann mit einem Container wie Spring geschehen, kann aber auch manuell erfolgen, indem Parameter an Objektkonstruktoren übergeben werden. Beide Möglichkeiten haben ihre Vor- und Nachteile, aber es ist definitiv nicht notwendig, DI in Ihrer Anwendung zu verwenden.
Eine Voraussetzung ist, dass die Dienste durch ihre Abhängigkeiten parametrisiert werden müssen. Was dies genau bedeutet, hängt von der Sprache und dem Ansatz eines bestimmten Systems ab. Normalerweise hat dies die Form von Konstruktorparametern, die Verwendung von Setters ist jedoch auch eine Option. Dies bedeutet auch, dass die Abhängigkeiten eines Dienstes (beim Aufrufen einer Dienstmethode) für die Benutzer des Dienstes verborgen sind.
Wann verwenden? Ich würde sagen, wann immer die Anwendung groß genug ist, um die Logik in separate Module einzukapseln, wobei ein Abhängigkeitsgraph zwischen den Modulen einen Gewinn an Lesbarkeit und Erkundbarkeit des Codes ergibt.
Abhängigkeitsinjektion ist die Praxis, entkoppelte Komponentenagnostiker von einigen ihrer Abhängigkeiten zu machen, dies folgt der SOLID -Richtlinie, die besagt
Prinzip der Inversion der Abhängigkeit: man sollte "auf Abstraktionen angewiesen sein, nicht Konkretionen.
Die bessere Implementierung von Dependency Injection ist das Kompositionswurzel-Entwurfsmuster, da Ihre Komponenten vom Abhängigkeitsinjektionscontainer entkoppelt werden können.
Ich empfehle diesen großartigen Artikel über Composition Root http://blog.ploeh.dk/2011/07/28/CompositionRoot/ , Geschrieben von Mark Seemann
hier sind wesentliche Punkte aus diesem Artikel:
Eine Kompositionswurzel ist ein (vorzugsweise) eindeutiger Ort in einer Anwendung wo Module zusammengesetzt sind.
...
Nur Anwendungen sollten Kompositionswurzeln haben. Bibliotheken und Frameworks sollten nicht.
...
Ein DI-Container sollte nur von der Kompositionswurzel aus referenziert werden. Alle anderen Module sollten keinen Bezug zum Container haben.
Die Dokumentation von Di-Ninja, einem Abhängigkeitsinjektions-Framework, ist ein sehr gutes Beispiel, um zu zeigen, wie die Prinzipien des Kompositionswurzels und der Abhängigkeitsinjektion funktionieren https://github.com/di-ninja/di-ninja Wie ich weiß, ist das einzige DiC in Javascript, das das Composition-Root-Entwurfsmuster implementiert.
Aus Christoffer Noring, Pablo Deelemans Buch "Learning Angular - Second Edition":
"Während unsere Anwendungen wachsen und sich weiterentwickeln, werden alle unsere Code-Entitäten intern Instanzen anderer Objekte erfordern, die besser als Abhängigkeiten in der Welt des Software-Engineerings bekannt sind. Die Weitergabe solcher Abhängigkeiten an den abhängigen Client wird als Injektion bezeichnet. Dies beinhaltet auch die Teilnahme einer anderen Code-Entität namens Injektor.Der Injektor übernimmt die Verantwortung für das Instanziieren und Bootstrapping der erforderlichen Abhängigkeiten, so dass sie ab dem Moment, in dem sie erfolgreich in den Client injiziert werden, einsatzbereit sind Der Client weiß nicht, wie er seine eigenen Abhängigkeiten instanziieren kann, und er kennt nur die Schnittstelle, die er implementiert, um sie zu nutzen. "
Abhängigkeitsinjektion ist eine Art Implementierung des " Inversion of Control " - Prinzips, auf dem Frameworks basieren.
Frameworks Wie in "Design Pattern" von GoF angegeben, sind Klassen, die die Hauptsteuerungslogik implementieren und den Entwickler dazu auffordern, dies zu tun. Auf diese Weise erkennen Frameworks das Prinzip der Umkehrung des Steuerelements.
Dieses IoC-Prinzip ist eine Methode zur Implementierung als Technik und nicht als Klassenhierarchie. Es handelt sich dabei lediglich um Abhängigkeitsinjektion.
DIbesteht hauptsächlich aus dem Delegieren der Zuordnung von Klasseninstanzen und der Typreferenz auf diese Instanzen an eine externe "Entität": ein Objekt, eine statische Klasse, eine Komponente, ein Framework usw.
Klasseninstanzen sind die "Abhängigkeiten", die externe Bindung der aufrufenden Komponente mit der Klasseninstanz durch die Referenz ist "injection".
Offensichtlich können Sie diese Technik in vielerlei Hinsicht aus Sicht von OOP implementieren, siehe zum Beispiel Konstruktorinjektion, Setterinjektion, Schnittstelleninjektion.
Das Delegieren eines Dritten zum Ausführen eines Abgleichs einer Referenz mit einem Objekt ist sehr nützlich, wenn Sie eine Komponente, die einige Dienste benötigt, von der gleichen Dienstimplementierung vollständig trennen möchten.
Auf diese Weise können Sie sich beim Entwerfen von Komponenten ausschließlich auf ihre Architektur und ihre spezifische Logik konzentrieren und auf Schnittstellen für die Zusammenarbeit mit anderen Objekten vertrauen, ohne sich um jegliche Art von Implementierungsänderungen der verwendeten Objekte/Services kümmern zu müssen, auch wenn dasselbe Objekt, das Sie verwenden wird komplett ersetzt (natürlich unter Berücksichtigung der Schnittstelle).
Jede nicht-triviale Anwendung besteht aus zwei oder mehr Klassen, die zusammenarbeiten, um Geschäftslogik auszuführen. Traditionell ist jedes Objekt dafür verantwortlich, seine eigenen Verweise auf die Objekte zu erhalten, mit denen es zusammenarbeitet (seine Abhängigkeiten). Beim Anwenden von DI werden die Objekte zum Zeitpunkt der Erstellung von einer externen Entität abhängig gemacht, die jedes Objekt im System koordiniert. Mit anderen Worten, Abhängigkeiten werden in Objekte eingefügt.
Weitere Details finden Sie unter Linkbeschreibung hier eingeben
DI ist, wie reale Objekte tatsächlich miteinander interagieren, ohne dass ein Objekt für die Existenz eines anderen Objekts verantwortlich ist. Objekte sollten gleich behandelt werden. Sie sind alle Objekte. Niemand sollte sich wie ein Schöpfer verhalten. So werden Sie Ihren Objekten gerecht.
Einfaches Beispiel :
wenn Sie einen Arzt brauchen, suchen Sie einfach einen (vorhandenen). Sie werden nicht daran denken, einen Arzt von Grund auf zu erstellen, um Ihnen zu helfen. Er existiert bereits und kann Ihnen oder anderen Objekten dienen. Er hat das Recht zu existieren, unabhängig davon, ob Sie (ein einzelner Gegenstand) ihn brauchen oder nicht, weil es sein Ziel ist, einen oder mehrere Gegenstände zu bedienen. Wer für seine Existenz entschieden hat, ist der allmächtige Gott, keine natürliche Auslese. Daher besteht ein Vorteil von DI darin, zu vermeiden, dass unnötige, redundante Objekte während der Lebensdauer Ihres Universums (d. H. Anwendung) ohne Zweck leben.