Zunächst möchte ich eine Annahme erklären, die ich für diese Antwort mache. Es ist nicht immer wahr, aber ziemlich oft:
Schnittstellen sind Adjektive; Klassen sind Substantive.
(Eigentlich gibt es Schnittstellen, die auch Substantive sind, aber ich möchte hier verallgemeinern.)
So kann eine Schnittstelle zum Beispiel etwas wie IDisposable
, IEnumerable
oder IPrintable
. Eine Klasse ist eine tatsächliche Implementierung einer oder mehrerer dieser Schnittstellen: List
oder Map
beide können Implementierungen von sein IEnumerable
.
Um es auf den Punkt zu bringen: Oft hängen Ihre Klassen voneinander ab. Sie könnten beispielsweise eine Database
Klasse haben, die auf Ihre Datenbank zugreift (hah, Überraschung! ;-)), aber Sie möchten auch, dass diese Klasse den Zugriff auf die Datenbank protokolliert. Angenommen, Sie haben eine andere Klasse Logger
und haben dann Database
eine Abhängigkeit von Logger
.
So weit, ist es gut.
Sie können diese Abhängigkeit innerhalb Ihrer Database
Klasse mit der folgenden Zeile modellieren :
var logger = new Logger();
und alles ist gut. Es ist bis zu dem Tag in Ordnung, an dem Sie feststellen, dass Sie eine Reihe von Protokollierern benötigen: Manchmal möchten Sie sich an der Konsole, manchmal am Dateisystem, manchmal mit TCP / IP und einem Remote-Protokollierungsserver usw. anmelden ...
Und natürlich möchten Sie NICHT Ihren gesamten Code ändern (in der Zwischenzeit haben Sie Unmengen davon) und alle Zeilen ersetzen
var logger = new Logger();
durch:
var logger = new TcpLogger();
Erstens macht das keinen Spaß. Zweitens ist dies fehleranfällig. Drittens ist dies eine dumme, sich wiederholende Arbeit für einen ausgebildeten Affen. Also, was machst du?
Offensichtlich ist es eine gute Idee, eine Schnittstelle ICanLog
(oder ähnliches) einzuführen , die von allen verschiedenen Loggern implementiert wird. Schritt 1 in Ihrem Code ist also, dass Sie Folgendes tun:
ICanLog logger = new Logger();
Jetzt ändert die Typinferenz den Typ nicht mehr, Sie haben immer eine einzige Schnittstelle, gegen die Sie entwickeln können. Der nächste Schritt ist, dass Sie nicht immer new Logger()
und immer wieder haben wollen. Sie setzen also die Zuverlässigkeit zum Erstellen neuer Instanzen auf eine einzelne zentrale Factory-Klasse und erhalten Code wie:
ICanLog logger = LoggerFactory.Create();
Die Fabrik selbst entscheidet, welche Art von Logger erstellt werden soll. Ihr Code ist nicht mehr wichtig, und wenn Sie den verwendeten Loggertyp ändern möchten, ändern Sie ihn einmal : Innerhalb der Fabrik.
Jetzt können Sie diese Factory natürlich verallgemeinern und für jeden Typ verwenden:
ICanLog logger = TypeFactory.Create<ICanLog>();
Irgendwo benötigt diese TypeFactory Konfigurationsdaten, deren tatsächliche Klasse instanziiert werden muss, wenn ein bestimmter Schnittstellentyp angefordert wird. Daher benötigen Sie eine Zuordnung. Natürlich können Sie diese Zuordnung in Ihrem Code vornehmen, aber dann bedeutet eine Typänderung eine Neukompilierung. Sie können diese Zuordnung aber auch in eine XML-Datei einfügen, z. Auf diese Weise können Sie die tatsächlich verwendete Klasse auch nach der Kompilierungszeit (!) Ändern, dh dynamisch, ohne sie neu zu kompilieren!
Um Ihnen ein nützliches Beispiel dafür zu geben: Stellen Sie sich eine Software vor, die sich nicht normal protokolliert. Wenn Ihr Kunde jedoch anruft und um Hilfe bittet, weil er ein Problem hat, senden Sie ihm lediglich eine aktualisierte XML-Konfigurationsdatei Die Protokollierung ist aktiviert, und Ihr Support kann die Protokolldateien verwenden, um Ihren Kunden zu helfen.
Und jetzt, wenn Sie Namen ein wenig ersetzen, erhalten Sie eine einfache Implementierung eines Service Locator , eines von zwei Mustern für die Umkehrung der Kontrolle (da Sie die Kontrolle darüber umkehren, wer genau entscheidet, welche Klasse instanziiert werden soll).
Alles in allem reduziert dies die Abhängigkeiten in Ihrem Code, aber jetzt ist Ihr gesamter Code von dem zentralen, einzelnen Service-Locator abhängig.
Die Abhängigkeitsinjektion ist jetzt der nächste Schritt in dieser Zeile: Beseitigen Sie einfach diese einzelne Abhängigkeit vom Service Locator: Anstatt dass verschiedene Klassen den Service Locator nach einer Implementierung für eine bestimmte Schnittstelle fragen, geben Sie erneut die Kontrolle darüber zurück, wer was instanziiert .
Mit der Abhängigkeitsinjektion verfügt Ihre Database
Klasse jetzt über einen Konstruktor, für den ein Parameter vom Typ erforderlich ist ICanLog
:
public Database(ICanLog logger) { ... }
Jetzt hat Ihre Datenbank immer einen Logger zu verwenden, aber sie weiß nicht mehr, woher dieser Logger kommt.
Und hier kommt ein DI-Framework ins Spiel: Sie konfigurieren Ihre Zuordnungen erneut und bitten dann Ihr DI-Framework, Ihre Anwendung für Sie zu instanziieren. Da die Application
Klasse eine ICanPersistData
Implementierung erfordert , wird eine Instanz von Database
injiziert. Dazu muss jedoch zuerst eine Instanz der Art von Logger erstellt werden, für die konfiguriert ist ICanLog
. Und so weiter ...
Um es kurz zu machen: Die Abhängigkeitsinjektion ist eine von zwei Möglichkeiten, um Abhängigkeiten in Ihrem Code zu entfernen. Es ist sehr nützlich für Konfigurationsänderungen nach der Kompilierungszeit und eignet sich hervorragend für Unit-Tests (da es das Einspritzen von Stubs und / oder Mocks sehr einfach macht).
In der Praxis gibt es Dinge, die Sie ohne einen Service Locator nicht tun können (z. B. wenn Sie nicht im Voraus wissen, wie viele Instanzen Sie für eine bestimmte Schnittstelle benötigen: Ein DI-Framework fügt immer nur eine Instanz pro Parameter ein, aber Sie können aufrufen natürlich ein Service-Locator innerhalb einer Schleife), daher stellt meistens jedes DI-Framework auch einen Service-Locator bereit.
Aber im Grunde ist es das.
PS: Was ich hier beschrieben habe, ist eine Technik namens Konstruktorinjektion . Es gibt auch eine Eigenschaftsinjektion, bei der keine Konstruktorparameter, sondern Eigenschaften zum Definieren und Auflösen von Abhängigkeiten verwendet werden. Stellen Sie sich die Eigenschaftsinjektion als optionale Abhängigkeit und die Konstruktorinjektion als obligatorische Abhängigkeiten vor. Eine Diskussion darüber würde jedoch den Rahmen dieser Frage sprengen.