Was ist Dependency Injection?

Funktion, Eigenschaften und Besonderheiten sowie Anwendungen von Dependency Injection
Dependency Injection, kurz DI, nimmt aufgrund der Auswirkungen auf die Code-Vereinfachung immer mehr zu. Wenn du noch keine Gelegenheit hattest, DI zu erlernen und anzuwenden, wird dich dieser Artikel mit den Konzepten vertraut machen und veranschaulichen, wie die Dependency Injection funktioniert.
Was ist Dependency Injection?
Beim Software-Engineering ist Dependency Injection eine Technik, bei der ein Objekt (oder eine statische Methode) die Abhängigkeiten eines anderen Objekts liefert. Eine Abhängigkeit wiederum ist ein Objekt, das verwendet werden kann (ein Dienst). Eine Injektion ist die Weitergabe einer Abhängigkeit an ein abhängiges Objekt (einen Client), das diese verwenden würde. Die Weitergabe des Dienstes an den Client, anstatt einem Client das Erstellen oder Finden des Dienstes zu ermöglichen, ist die grundlegende Anforderung des Musters bei der Dependency Injection.
Die Absicht hinter Dependency Injection (DI) ist es, die Kopplung zwischen Komponenten in einem Softwaresystem zu reduzieren und dadurch Flexibilität, Testbarkeit und Wartbarkeit des Codes zu erhöhen. Die Dependency Injection ist eine Form der umfassenderen Technik der Inversion der Kontrolle. Wie bei anderen Formen der Inversion der Kontrolle unterstützt die Dependency Injection das Prinzip der Abhängigkeitsinversion. Der Client überträgt die Verantwortung für die Bereitstellung seiner Abhängigkeiten an externen Code (den Injektor). Der Client darf den Injector-Code nicht aufrufen. Dies ist der Injection-Code, der die Dienste erstellt und den Client auffordert, sie einzuspeisen. Dies bedeutet, dass der Clientcode nicht über den einzuführenden Code Bescheid wissen muss, wie die Services erstellt werden sollen oder welche Services tatsächlich verwendet werden. Der Client muss nur die intrinsischen Schnittstellen der Services kennen, da diese festlegen, wie der Client die Services verwenden darf. Dies trennt die Verantwortlichkeiten für Nutzung und Konstruktion.
Es gibt drei gängige Mittel, mit denen ein Client eine Dependency Injection akzeptieren kann: Setter-, Schnittstellen- und Konstruktorbasierte Injektion. Setter und Konstruktorinjektion unterscheiden sich hauptsächlich darin, wann sie verwendet werden können. Die Schnittstelleninjektion unterscheidet sich dadurch, dass der Abhängigkeit die Möglichkeit gegeben wird, die eigene Injektion zu steuern. Für jedes muss ein separater Konstruktionscode (der Injektor) die Verantwortung dafür übernehmen, dass ein Client und seine Abhängigkeiten zueinander eingeführt werden.
Ziele und Auswirkungen von Dependency Injection in a Nutshell
- Entkopplung
Anstatt dass eine Klasse ihre Abhängigkeiten selbst erzeugt, werden ihr diese von außen „injiziert“. Dadurch hängt die Klasse nicht mehr direkt von konkreten Implementierungen ab. - Erleichtertes Testen
Da Abhängigkeiten von außen geliefert werden, kann man sie in Tests leicht durch Mocks oder Stubs ersetzen. - Bessere Wartbarkeit & Erweiterbarkeit
Änderungen an einer Abhängigkeit wirken sich weniger stark auf abhängige Komponenten aus. Neue Implementierungen können einfacher ausgetauscht oder ergänzt werden. - Zentralisierte Konfiguration
Die Verwaltung von Abhängigkeiten (z. B. durch ein DI-Framework) erlaubt eine bessere Übersicht und Kontrolle über die Systemarchitektur. - Förderung von Best Practices
DI fördert sauberes, modularisiertes Design, z. B. durch die Einhaltung des Single Responsibility Principle oder die Verwendung von Interfaces.
Dependency Injection trennt das Was vom Wie. Eine Komponente sagt, was sie braucht – aber nicht, wie sie es bekommt.
Wie funktioniert Dependency Injection?
Dependency Injection löst Probleme wie:
- Wie kann eine Anwendung oder Klasse unabhängig von der Erstellung ihrer Objekte sein?
- Wie kann festgelegt werden, wie Objekte in separaten Konfigurationsdateien erstellt werden?
- Wie kann festgelegt werden, wie Objekte in separaten Konfigurationsdateien erstellt werden?
Das Erstellen von Objekten direkt innerhalb der Klasse, für die die Objekte erforderlich sind, ist unflexibel, da die Klasse für bestimmte Objekte festgelegt wird und die spätere Änderung der Instantiierung nicht unabhängig von der Klasse (ohne Änderung dieser) möglich ist. Dadurch wird verhindert, dass sie wieder verwendet werden kann, wenn andere Objekte erforderlich sind. Außerdem ist die Klasse schwer zu testen, da echte Objekte nicht durch Scheinobjekte ersetzt werden können.
Eigenschaften und Besonderheiten von Dependency Injection. In welchen Frameworks und Programmiersprachen kann ich Depedendency Injection nutzen?
Die Dependency Injection ist eine beliebte Alternative zum Service Locator-Muster. Viele moderne Anwendungsframeworks implementieren diese Technik und diese Frameworks stellen die technischen Teile der Technik bereit, sodass du dich auf die Implementierung deiner Geschäftslogik konzentrieren kannst. Beliebte Beispiele sind:
- Spring (Java)
- Google Guice (Java)
- Dagger (Java and Android)
- Castle Windsor (.NET)
- Unity(.NET)
- Wallaroo (C++)
- Hypodermic (C++)
- Robotlegs (Actionscript)
- LightWire (ColdFusion)
- Orochi (Perl]
- Phemto (PHP 5)
- Laravel (PHP 5)
- SpringPython (Python)
- Zenject (Unity 3D)
Abhängigkeitsinjektion kann als eine Disziplin angewendet werden, die dazu auffordert, dass alle Objekte Konstruktion und Verhalten voneinander trennen. Die Verwendung eines DI-Frameworks für die Ausführung von Konstruktionen kann dazu führen, dass die Verwendung des neuen Schlüsselworts oder die direkte Erstellung von Wertobjekten verboten wird.
Codebeispiele zur Dependency Injection
Java
Anbei haben wir ein kleines Codebeispiel in Java, das die Verwendung von DI beispielhaft zeigt.
Wir haben jeweils den Code ohne DI und den Code unter Verwendung des DI-Pattern aufgeführt.
Ohne DI
public class Service {
public void ausführen() {
System.out.println("Service wird ausgeführt");
}
}
public class Client {
private Service service;
public Client() {
this.service = new Service(); // direkte Kopplung
}
public void starten() {
service.ausführen();
}
}
Die Client
-Klasse ist direkt an Service
gekoppelt. Ein Austausch oder Test ist nur mit Aufwand möglich.
Mit DI
public class Service {
public void ausführen() {
System.out.println("Service wird ausgeführt");
}
}
public class Client {
private Service service;
// Dependency Injection via Konstruktor
public Client(Service service) {
this.service = service;
}
public void starten() {
service.ausführen();
}
}
// Nutzung
public class Main {
public static void main(String[] args) {
Service service = new Service();
Client client = new Client(service); // Injection erfolgt hier
client.starten();
}
}
Der Client
ist nicht mehr direkt verantwortlich für die Instanziierung. Dadurch ist die Klasse leichter testbar, flexibler und weniger abhängig von einer konkreten Implementierung.
Warum sollte ich Dependency Injection nutzen?
Dependency Injection Abhängigkeitsinjektion wird in der Entwicklergemeinde immer beliebter. Inversion of Control spricht darüber, wer den Anruf einleiten soll, während die Dependency Injection davon spricht, wie ein Objekt durch Abstraktion eine Abhängigkeit von einem anderen Objekt erlangt. Wenn du Dependency Injection verwendest, kannst du zwischen verschiedenen Stilen wählen. Dieses Entwurfmuster hilft auch bei der Klassenentkopplung und macht es einem Entwickler leicht, Abhängigkeiten zwischen Objekten zu verwalten. Dadurch wird es dir einfacher fallen, zusammenhängende Funktionen in deinen eigenen Vertrag zu integrieren. Infolgedessen wird der Code stärker modularisiert. Dependency Injection erhöht auch die Wiederverwendbarkeit des Codes und verbessert die Wartbarkeit und das Testen von Codes.
Exkurs: Dependency Injection im Vergleich zu anderen Pattern
Kriterium | Dependency Injection (DI) | Service Locator | Factory Pattern | Singleton Pattern |
---|---|---|---|---|
Ziel | Entkopplung durch externe Bereitstellung von Abhängigkeiten | Zentrale Stelle zur Bereitstellung von Services | Erzeugung von Objekten über eine spezielle Klasse | Sicherstellung, dass nur eine Instanz existiert |
Abhängigkeit sichtbar? | ✅ Ja – durch Konstruktor, Setter oder Interface | ❌ Nein – Abhängigkeit wird intern bezogen | ✅ Teilweise – meist über Aufruf der Factory | ❌ Nein – Instanz wird intern gehalten |
Testbarkeit | ✅ Sehr gut – einfach zu mocken | 🔶 Mäßig – schwieriger zu mocken | 🔶 Gut – wenn Factory austauschbar ist | ❌ Eingeschränkt – schwer zu mocken |
Entkopplung | ✅ Hoch – keine feste Bindung an konkrete Implementierung | 🔶 Mittel – zentrale Abhängigkeit zum Locator | 🔶 Mittel – Factory erzeugt konkrete Klassen | ❌ Gering – feste Kopplung an Singleton |
Komplexität | 🔶 Mittel – mehr Boilerplate | 🔶 Mittel – Service-Registry muss gepflegt werden | 🔶 Mittel – zusätzliche Factory-Klassen | ✅ Gering – einfache Implementierung |
Verwendung in Frameworks | Sehr verbreitet (z. B. Spring, Angular) | Selten, aber z. B. in Legacy-Systemen genutzt | Oft genutzt zur Objekt-Erzeugung z. B. bei DAO, UI | Verbreitet, aber mit Vorsicht zu genießen |
Dependency Injection fördert lose Kopplung, ist testfreundlich und ideal für moderne Architekturen.
Service Locator kann zu versteckten Abhängigkeiten führen.
Factory eignet sich gut zur zentralisierten Objekt-Erzeugung.
Singleton sollte sparsam eingesetzt werden, da es globale Zustände einführt.
Artikel aktualisiert: 20. Mai 2025
Rückmeldungen