Als Dependency Injection (DI, englisch dependency ‚Abhängigkeit‘ und injection ‚Injektion‘, deutsch Abhängigkeitsinjektion[1] oder Einbringen von Abhängigkeiten) wird in der objektorientierten Programmierung ein Entwurfsmuster bezeichnet, welches die Abhängigkeiten eines Objekts zur Laufzeit reglementiert: Benötigt ein Objekt beispielsweise bei seiner Initialisierung ein anderes Objekt, ist diese Abhängigkeit an einem zentralen Ort hinterlegt – es wird also nicht vom initialisierten Objekt selbst erzeugt.

Wortbedeutung

[Bearbeiten | Quelltext bearbeiten]

Die Bezeichnung Dependency Injection wurde 2004 vom Softwareentwickler Martin Fowler eingeführt, um den damaligen Begriff Inversion of Control zu präzisieren: “Inversion of Control is too generic a term, and thus people find it confusing. As a result with a lot of discussion with various [Inversion of Control] advocates we settled on the name Dependency Injection.” (Martin Fowler: martinfowler.com)[2]

Hintergründe

[Bearbeiten | Quelltext bearbeiten]

Mit Dependency Injection ist es möglich – entsprechend dem Single-Responsibility-Prinzip – die Verantwortlichkeit für den Aufbau des Abhängigkeitsnetzes zwischen den Objekten eines Programmes aus den einzelnen Klassen in eine zentrale Komponente zu überführen.

In einem herkömmlichen System objektorientierter Programmierung ist dagegen jedes Objekt selbst dafür zuständig, seine Abhängigkeiten – also benötigte Objekte und Ressourcen – zu verwalten. Dafür muss jedes Objekt einige Kenntnisse seiner Umgebung mitbringen, die es zur Erfüllung seiner eigentlichen Aufgabe normalerweise nicht benötigen würde.

Dependency Injection überträgt die Verantwortung für das Erzeugen und die Verknüpfung von Objekten an eine eigenständige Komponente, wie beispielsweise ein extern konfigurierbares Framework. Dadurch wird der Code des Objektes unabhängiger von seiner Umgebung. Das kann Abhängigkeiten von konkreten Klassen beim Kompilieren vermeiden und erleichtert besonders die Erstellung von Unit-Tests.

Vorteile und Nachteile

[Bearbeiten | Quelltext bearbeiten]

Vorteile

[Bearbeiten | Quelltext bearbeiten]

Nachteile

[Bearbeiten | Quelltext bearbeiten]

Struktur

[Bearbeiten | Quelltext bearbeiten]
Ein Beispiel für ein UML-Klassendiagramm und Sequenzdiagramm für das Dependency-Injection-Entwurfsmuster.

Im UML-Klassendiagramm instanziiert die Client-Klasse, für die die Objekte ServiceA und ServiceB erforderlich sind, die Klassen ServiceA1 und ServiceB1 nicht direkt. Stattdessen erstellt eine Injector-Klasse die Objekte und injiziert sie in den Client, wodurch der Client unabhängig davon wird, wie die Objekte erstellt werden.

Das UML-Sequenzdiagramm zeigt die Laufzeitinteraktionen: Das Injector-Objekt erstellt die Objekte ServiceA1 und ServiceB1. Danach erstellt der Injector das Client-Objekt und injiziert die Objekte ServiceA1 und ServiceB1.

Umsetzung

[Bearbeiten | Quelltext bearbeiten]

Martin Fowler beschreibt drei verschiedene Arten zum Setzen benötigter Referenzen, die er mit dem Begriff Dependency Injection verbindet: Constructor Injection, Interface Injection und Setter Injection (Abschnitt Forms of Dependency Injection in[2]). Alle von ihm geschilderten Verfahrensweisen verwenden dabei Methodenaufrufe, bei denen die zu setzenden Abhängigkeiten nicht Rückgabewerte, sondern Parameter sind.

Constructor Injection

[Bearbeiten | Quelltext bearbeiten]

Abhängigkeiten von anderen Klassen werden über Konstruktoren zur Verfügung gestellt.

class Abhaengiges {
  private Abhaengigkeit abhaengigkeit;

  //Konstruktor
  public Abhaengiges(final Abhaengigkeit abhaengigkeit) {
    this.abhaengigkeit = abhaengigkeit;
  }
}

class Injizierer {
  void methode() {
    final Abhaengigkeit abhaengigkeit = ... ;
    final Abhaengiges abhaengiges = new Abhaengiges(abhaengigkeit);
  }
}

Interface Injection

[Bearbeiten | Quelltext bearbeiten]

Das Modul der injizierenden Klasse definiert eine Schnittstelle, die von abhängigen Klassen implementiert werden muss, um zur Laufzeit die Abhängigkeiten zur Verfügung gestellt zu bekommen.

interface Injizierbar {
  void injiziere(final Abhaengigkeit abhaengigkeit);
}

class Abhaengiges implements Injizierbar {
  private final Abhaengigkeit abhaengigkeit;

  public void injiziere(final Abhaengigkeit abhaengigkeit) {
    this.abhaengigkeit = abhaengigkeit;
  }
}

class Injizierer {
  void methode() {
    final Abhaengigkeit abhaengigkeit = ... ;
    final Injizierbar abhaengiges = ... ;
    abhaengiges.injiziere(abhaengigkeit);
  }
}

Setter Injection

[Bearbeiten | Quelltext bearbeiten]

Die abhängige Klasse stellt Methoden zur Verfügung, die dazu verwendet werden, die Abhängigkeiten zur Verfügung zu stellen.

class Abhaengiges {
  private Abhaengigkeit abhaengigkeit;

  public void setAbhaengigkeit(final Abhaengigkeit abhaengigkeit) {
    this.abhaengigkeit = abhaengigkeit;
  }
}

class Injizierer {
  void methode() {
    final Abhaengiges abhaengiges = ... ;
    final Abhaengigkeit abhaengigkeit = ... ;
    abhaengiges.setAbhaengigkeit(abhaengigkeit);
  }
}

Weitere Umsetzungsmöglichkeiten

[Bearbeiten | Quelltext bearbeiten]

Es ist auch möglich, Dependency Injection auf andere Weisen[4] umzusetzen, wie sie in manchen Frameworks Verwendung finden. Beispielsweise können Abhängigkeiten nach Möglichkeiten der Programmiersprache durch Reflexion oder durch direktes Setzen des Verweises darauf in den Speicher auch ohne Methodenaufrufe gesetzt werden.

Es existieren verschiedene Frameworks für diverse Programmiersprachen und Plattformen zur Umsetzung, die fertige Lösungen zur Verfügung stellen. Diese implementieren das Muster mit zum Teil umfassender, weiterführender Funktionalität, wie beispielsweise das Einlesen der Konfiguration aus Dateien und deren Prüfung auf formale Korrektheit. Siehe dazu die Liste von Dependency Injection Frameworks.

Nachteilig kann sich je nach verwendetem DI-Framework auswirken, dass Programmlogik in Konfigurationsdateien ausgelagert werden muss, was die Übersichtlichkeit vermindern und die Wartung erschweren kann: die Entwickler müssen nun zum Verstehen des Codes noch die Konfiguration berücksichtigen, welche sich zudem manchen Hilfsmitteln der Codeanalyse (z. B. IDE-unterstütztes Finden von Abhängigkeiten oder Refactoring) entzieht.

Dieser Nachteil lässt sich vermeiden, indem die Dependency Injection ähnlich dem Entwurfsmuster Abstrakte Fabrik ohne die Verwendung eines Frameworks als Teil des Programms selbst implementiert wird. Diese Art der Umsetzung wird „Do-It-Yourself Dependency Injection“, kurz „DIY-DI“ genannt.[5]

Einzelnachweise

[Bearbeiten | Quelltext bearbeiten]
  1. Dependency Injection in ASP.NET Core, Microsoft Docs
  2. a b Martin Fowler: Inversion of Control Containers and the Dependency Injection pattern. 23. Januar 2004 (englisch) abgerufen am 16. Mai 2013.
  3. How Dependency Injection (DI) Works In Spring Java Application Development. DZone
  4. Ben Yu: Dependency Injection Types. (Memento vom 30. April 2006 im Internet Archive) (englisch) abgerufen am 16. Mai 2013.
  5. Chad Parry: DIY-DI (PDF; 549 kB) 9. März 2010. (englisch) abgerufen am 29. Juni 2014.