Was ist Dependency Injection (DI)?
Wie andere gesagt haben, Dependency Injection(DI) die Verantwortung für die direkte Erstellung und Verwaltung der Lebensdauer anderer Objektinstanzen, von denen unsere Klasse von Interesse (Verbraucherklasse) abhängt, abnimmt (in der UML-Sinn ). Diese Instanzen werden stattdessen an unsere Verbraucherklasse übergeben, normalerweise als Konstruktorparameter oder über Eigenschaftssetzer (die Verwaltung der Instanzierung des Abhängigkeitsobjekts und die Übergabe an die Verbraucherklasse wird normalerweise von einer Inversion der Kontrolle (IoC) Container, aber das ist ein anderes Thema).
DI, DIP und SOLID
Speziell im Paradigma von Robert C. Martins SOLID-Grundsätze für objektorientiertes Design , DI
ist eine der möglichen Implementierungen des Prinzip der Umkehrung von Abhängigkeiten (DIP) . Die DIP ist die D
der SOLID
Mantra - Andere DIP-Implementierungen umfassen die Muster Service Locator und Plugin.
Ziel des DIP ist es, enge, konkrete Abhängigkeiten zwischen Klassen zu entkoppeln und stattdessen die Kopplung durch eine Abstraktion zu lockern, die über eine interface
, abstract class
o pure virtual class
je nach der verwendeten Sprache und Vorgehensweise.
Ohne das DIP ist unser Code (ich nenne ihn "konsumierende Klasse") direkt an eine konkrete Abhängigkeit gekoppelt und ist außerdem oft mit der Verantwortung belastet, zu wissen, wie man eine Instanz dieser Abhängigkeit erhält und verwaltet, d.h. konzeptionell:
"I need to create/use a Foo and invoke method `GetBar()`"
In der Erwägung, dass nach der Anwendung des DIP die Anforderung gelockert wird und die Sorge um die Erlangung und Verwaltung der Lebensdauer des Foo
Abhängigkeit wurde entfernt:
"I need to invoke something which offers `GetBar()`"
Warum DIP (und DI) verwenden?
Die Entkopplung von Abhängigkeiten zwischen Klassen auf diese Weise ermöglicht einfache Substitution dieser Abhängigkeitsklassen mit anderen Implementierungen, die ebenfalls die Voraussetzungen der Abstraktion erfüllen (z.B. kann die Abhängigkeit mit einer anderen Implementierung der gleichen Schnittstelle getauscht werden). Außerdem können, wie bereits erwähnt, möglicherweise die Der häufigste Grund für die Entkopplung von Klassen über das DIP ist, dass eine konsumierende Klasse isoliert getestet werden kann, da dieselben Abhängigkeiten nun stubbed und/oder mocked werden können.
Eine Folge von DI ist, dass die Verwaltung der Lebensdauer von Instanzen von Abhängigkeitsobjekten nicht mehr von einer konsumierenden Klasse kontrolliert wird, da das Abhängigkeitsobjekt nun an die konsumierende Klasse übergeben wird (über Konstruktor- oder Setter-Injektion).
Dies kann auf unterschiedliche Weise betrachtet werden:
- Wenn die Lebenszeitkontrolle über die Abhängigkeiten durch die konsumierende Klasse beibehalten werden muss, kann die Kontrolle wiederhergestellt werden, indem eine (abstrakte) Fabrik zur Erzeugung der Instanzen der Abhängigkeitsklasse in die Konsumentenklasse injiziert wird. Der Konsument kann die Instanzen über eine
Create
in der Fabrik, und entsorgen Sie diese Instanzen, sobald sie fertig sind.
- Oder die Kontrolle über die Lebensdauer von Abhängigkeitsinstanzen kann an einen IoC-Container abgegeben werden (mehr dazu weiter unten).
Wann sollte man DI verwenden?
- Wenn es wahrscheinlich notwendig ist, eine Abhängigkeit durch eine gleichwertige Implementierung zu ersetzen,
- Immer dann, wenn Sie die Methoden einer Klasse isoliert von ihren Abhängigkeiten testen müssen,
- Wenn die Ungewissheit über die Lebensdauer einer Abhängigkeit ein Experiment rechtfertigt (z. B. Hey,
MyDepClass
ist thread-sicher - was, wenn wir es zu einem Singleton machen und dieselbe Instanz in alle Verbraucher injizieren?)
Ejemplo
Hier ist eine einfache C#-Implementierung. Gegeben die unten Consuming Klasse:
public class MyLogger
{
public void LogRecord(string somethingToLog)
{
Console.WriteLine("{0:HH:mm:ss} - {1}", DateTime.Now, somethingToLog);
}
}
Obwohl es scheinbar harmlos ist, hat es zwei static
Abhängigkeiten von zwei anderen Klassen, System.DateTime
y System.Console
die nicht nur die Optionen für die Protokollierung einschränken (die Protokollierung auf der Konsole ist wertlos, wenn niemand zuschaut), sondern, was noch schlimmer ist, angesichts der Abhängigkeit von einer nicht-deterministischen Systemuhr schwer automatisch zu testen sind.
Wir können jedoch anwenden DIP
zu dieser Klasse, indem die Zeitstempelung als Abhängigkeit abstrahiert wird und die MyLogger
nur auf eine einfache Schnittstelle:
public interface IClock
{
DateTime Now { get; }
}
Wir können auch die Abhängigkeit von Console
zu einer Abstraktion, wie zum Beispiel einer TextWriter
. Dependency Injection wird typischerweise implementiert als entweder 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 eine setXyz()
Setter oder eine .Net-Eigenschaft mit {set;}
definiert). Constructor Injection ist zu bevorzugen, da dies garantiert, dass sich die Klasse nach der Konstruktion in einem korrekten Zustand befindet, und es erlaubt, die internen Abhängigkeitsfelder als readonly
(C#) oder final
(Java). Wenn wir also die Konstruktorinjektion auf das obige Beispiel anwenden, ergibt sich folgendes Bild:
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);
}
}
(Eine konkrete Clock
zur Verfügung gestellt werden muss, die natürlich auch auf DateTime.Now
und die beiden Abhängigkeiten müssen von einem IoC-Container über Konstruktorinjektion bereitgestellt werden)
Ein automatisierter Unit Test kann erstellt werden, der definitiv beweist, dass unser Logger korrekt arbeitet, 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
Dependency Injection ist immer mit einer Inversion of Control Container (IoC) , um die konkreten Abhängigkeitsinstanzen zu injizieren (bereitzustellen) und um die Lebenszeitinstanzen zu verwalten. Während des Konfigurations-/Bootstrapping-Prozesses, IoC
Container ermöglichen es, Folgendes zu definieren:
- Abbildung zwischen jeder Abstraktion und der konfigurierten konkreten Implementierung (z. B. "jedes Mal, wenn ein Verbraucher einen Antrag auf
IBar
Rückgabe a ConcreteBar
Instanz" )
- Es können Richtlinien für die Verwaltung der Lebensdauer jeder Abhängigkeit festgelegt werden, z. B. für die Erstellung eines neuen Objekts für jede Verbraucherinstanz, für die gemeinsame Nutzung einer Singleton-Abhängigkeitsinstanz durch alle Verbraucher, für die gemeinsame Nutzung derselben Abhängigkeitsinstanz nur durch denselben Thread, usw.
- In .Net sind IoC-Container mit Protokollen wie
IDisposable
und wird die Verantwortung übernehmen für Disposing
Abhängigkeiten im Einklang mit dem konfigurierten Lebensdauermanagement.
Sobald IoC-Container konfiguriert / gebootstrappt wurden, arbeiten sie in der Regel nahtlos im Hintergrund, so dass sich der Programmierer auf den eigentlichen Code konzentrieren kann, anstatt sich um Abhängigkeiten zu kümmern.
Der Schlüssel zu DI-freundlichem Code liegt darin, statische Kopplung von Klassen zu vermeiden und new() nicht für die Erstellung von Abhängigkeiten zu verwenden
Wie im obigen Beispiel erfordert die Entkopplung von Abhängigkeiten einen gewissen Entwurfsaufwand, und für den Entwickler ist ein Paradigmenwechsel erforderlich, um mit der Gewohnheit zu brechen new
Abhängigkeiten zu vermeiden und stattdessen dem Container zu vertrauen, der die Abhängigkeiten verwaltet.
Aber die Vorteile sind vielfältig, vor allem die Möglichkeit, die Klasse, die Sie interessiert, gründlich zu testen.
Hinweis : Die Erstellung / Kartierung / Projektion (via new ..()
) von POCO / POJO / Serialisierungs-DTOs / Entitätsgraphen / anonymen JSON-Projektionen u. a. - d. h. "Nur-Daten"-Klassen oder -Datensätze - verwendet oder von Methoden zurückgegeben werden no als Abhängigkeiten (im Sinne der UML) betrachtet und unterliegen nicht der DI. Verwendung von new
diese zu projizieren, ist genau richtig.
0 Stimmen
Siehe meine Diskussion über Dependency Injection Hier .
44 Stimmen
Ich stimme den Kommentaren zu den Links zu. Ich kann verstehen, dass Sie vielleicht auf jemand anderen verweisen wollen. Aber fügen Sie zumindest hinzu, warum Sie sie verlinken und was diesen Link besser macht als die anderen Links, die ich mit Google finden könnte
0 Stimmen
@AR: Technisch gesehen, ist Dependency Injection no eine besondere Form von IoC. Vielmehr ist IoC eine Technik, die zur Bereitstellung von Dependency Injection verwendet wird. Es können auch andere Techniken für die Dependency Injection verwendet werden (obwohl IoC die einzige ist, die häufig verwendet wird), und IoC wird auch für viele andere Probleme verwendet.
157 Stimmen
Was die Links betrifft, so ist zu bedenken, dass sie oft auf die eine oder andere Weise verschwinden. Die Zahl der toten Links in SO-Antworten steigt. Egal, wie gut der verlinkte Artikel ist, er nützt nichts, wenn man ihn nicht finden kann.
0 Stimmen
Vojta Jina über Dependency Injection youtu.be/_OGGsf1ZXMs . Der erste Teil.
0 Stimmen
Ein Überblick über Dependency Injection und ihre Beziehung zu anderen OOP-Prinzipien: deviq.com/dependency-injection
0 Stimmen
Hier ist ein Anwendungsbeispiel ohne DI: tugay.biz/2017/05/standalone-jpa-beispiel-mit-hibernate.html und dasselbe mit DI: tugay.biz/2017/05/add-c3p0-to-previous-example.html