Dependency Inversion / Inversion of Control / Dependency Injection
https://oer-informatik.de/dependency-inversion
tl/dr; (ca. 4 min Lesezeit): Vom Designprinzip “Dependency Inversion” über die Umsetzung “Inversion of Control” zum Designpattern Dependency Injection.
In der objektorientierten Programmierung (OOP) gibt es eine Reihe von grundlegenden Prinzipien, die die Wartbarkeit, Lesbarkeit und Fehlerresistenz von Code positiv beeinflussen. Diese abstrakten Prinzipien werden Design Principles (Entwurfsprinzipien) genannt. In Anwendung dieser Prinzipen wurden eine Reihe best practices veröffentlicht, die Standardprobleme der OOP lösen und einheitliche Vokabeln für die genutzten Softwarekomponenten festlegen. Diese konkreten Lösungsvorschläge nennt man Design Pattern (Entwurfsmuster). Oft setzen Entwurfsmuster eine Reihe von abstrakten Designprinzipen um - wie hier am Beispiel des Dependency Inversion-Designprinzipts und dem Entwurfsmuster Dependency Injection gezeigt wird.
Das Dependency Inversion Prinzip
Das Dependency Inversion Principle beschäftigt sich mit der Entkopplung von Softwaremodulen untereinander. Vereinfacht gesagt besagt es, dass zwei konkrete Klassen nicht direkt von einandere abhängen sollen (und damit eng gekoppelt sind), sondern die Abhängigkeit über ein Interface (eine abstrakte Struktur) realisiert werden sollte.
Es ist eines der fünf grundlegenden S.O.L.I.D.-Prinzipien, die Robert C. Martin in seinem Clean Code-Buch veröffentlicht hat.
Kokreter besagt es, dass high level-Module nicht direkt von low level-Modulen abhängen sollen. Diese Abhängigkeit lässt sich im UML-Klassendiagramm folgendermaßen darstellen:

Ein Beispiel mit Quelltext. Wir betrachten einen High-Level Client, der eine Instanz des Low-Level Services erzeugt und nutzt (hier: als Attribut). Der Client hängt von der Implementierung des Services ab:
public class Client {
private ConcreteService service;
public Client(){
service = new ConcreteService();
}
}
public class ConcreteService {/*...*/};
Die Abhängigkeit in diesem Beispiel ist konkret eine einseitig navigierbare Assoziation:

Das Dependency Inversion Prinzip besagt:
High-Level Module sollen nicht von Low-Level-Modulen abhängen. Beide sollen von Abstraktionen abhängen.
Die Abhängigkeit soll also aufgelöst werden, in dem ein Interface die Abhängigkeit zwischen Client und Server entkoppelt. Das Interface wird von der High-Level-Klasse genutzt und von der Low-Level-Klasse implementiert:
public class Client {
private ServiceInterface service;
public Client(){
service = new ConcreteService();
}
}
public class ConcreteService implements ServiceInterface {/*...*/};
public interface ServiceInterface {/*...*/};
Im UML-Klassendiagramm bleibt die einseitig navigierbare Assoziation erhalten, zielt jedoch auf das Interface. Auch die Abhängigkeit des ConcreteService (Implementierung) zeigt auf das Interface. Beide hängen also nur noch von der abstrakten Struktur des ServiceInterface ab.

Wenn Assoziationen über Interfaces realisiert werden, werden diese in UML-Klassendiagrammen häufig in der “Lollipop”-Notation dargestellt, die das Interface nicht mehr ausführlich, sondern nur noch als Stecker/Buchse skizziert darstellt:

Objekterzeugung entkoppeln: Inversion of Control (IoC)
Ein weiterer Schritt der Entkopplung wird unter dem Prinzip Inversion of Control (IoC) zusammengefasst:
Die Kontrolle darüber, welche konkrete Implementierung eines Service aufgerufen wird, wird vom eigentlichen Aufruf der Methode entkoppelt - die Konrolle also umgekehrt.
Die Erzeugung einer Objektinstanz einer abstrahierten Klasse (hier: ServiceInterface) soll von deren Nutzung (hier: durch Client) entkoppelt werden. Wir übergeben die Kontrolle über die Objektinstanz nach außen (z.B. über Setter oder Konstruktoren):

Im Code einer Client-Klasse soll also nicht selbst festlegt werden, welche Klassen (oder Bibliotheken) die aufgerufenen Methoden des ServiceInterface implementieren, sondern dies soll an anderer Stelle (i.d.R. durch ein Framework) erfolgen.
Dies setzt zunächst das dependency inversion principle (DIP) voraus.
Im einfachsten Fall geschieht dies über Parameter, die bei Methodenaufrufen von extern übergeben werden.
Ein Beispiel mit Konstruktoren:
public class Client {
private ServiceInterface service;
public Client(ServiceInterface service){
this.service = service;
}
}
oder ein Beispiel mit Setter-Methoden:
public class Client {
private ServiceInterface service;
public void setService(ServiceInterface service){
this.service = service;
}
}
Das konkrete Verhalten des jeweiligen Methodenaufrufs kann somit außerhalb der Klasse geändert werden - abhängig davon, welche konkrete Implementierung mit dem Methodenaufruf später verknüpft wird. Hierzu werden in den implementierten Codeabschnitten abstrakte Strukturen genutzt, die beispielsweise im Framework implementiert werden müssen.
Vorteil dieser Technik ist eine Entkopplung des Methodenaufrufs von der Implementierung, damit verbunden
bessere Modularität, Klassen können beispielsweise auch leicht getauscht werden
leichter Testbarkeit, da Abhängigkeiten leichter über Mock-Objekte darstellbar sind
Praktisch umgesetzt werden kann IoC über die Design Pattern:
Dependency Injection
Factory Pattern
Strategy Pattern
Service Locator Pattern
Dependency Injection
Bei dependency injection (DI) wird die Übergabe der konkreten Instanz nicht mehr (wie noch im obigen Beispiel) über die Implementierung des Codes selbst umgesetzt, sondern durch das Framework. Neben den beiden Klassen Client
und Service
, die per DIP entkoppelt wurden, gibt es nun einen weiteren Akteur: den Injector, der i.d.R. im Framework implementiert ist.

Hierbei werden drei unterschiedliche Arten der Injezierung genutzt:
constructor injection: Die konkrete Instanz wird als Parameter eines Konstruktors übergeben
setter injection: Die konkrete Instanz wird als Parameter eines Setters übergeben
field injection
Links und weitere Informationen
- https://www.baeldung.com/inversion-control-and-dependency-injection-in-spring
- https://stackify.com/dependency-injection/
- https://martinfowler.com/articles/injection.html
Quellen und offene Ressourcen (OER)
Die Ursprungstexte (als Markdown), Grafiken und zugrunde liegende Diagrammquelltexte finden sich (soweit möglich in weiterbearbeitbarer Form) in folgendem git-Repository:
https://gitlab.com/oer-informatik/design-pattern/dependency-injection/.
Sofern nicht explizit anderweitig angegeben sind sie zur Nutzung als Open Education Resource (OER) unter Namensnennung (H. Stein, oer-informatik.de) freigegeben gemäß der Creative Commons Namensnennung 4.0 International Lizenz (CC BY 4.0).