Abhängigkeiten selbst gestalten: Mit Dependency Injection zu flexiblem Software-Design

Seite: 2/2

Anbieter zum Thema

Störende Abhängigkeiten „brechen“ mit dem Dependency Inversion Principle (DIP)

Um zyklische Abhängigkeiten zu vermeiden und die Beziehungssituation zwischen Softwarebausteinen deutlich flexibler und angenehmer zu gestalten, sollte man das Dependency Inversion Principle (DIP) befolgen:

  • A. Module höherer Ebenen sollten nicht von Modulen niedrigerer Ebenen abhängen. Beide sollten von Abstraktionen abhängen.
  • B. Abstraktionen sollten nicht von Details abhängen. Details sollten von Abstraktionen abhängen.
Bildergalerie
Bildergalerie mit 5 Bildern

Hierbei ist mit „Modul höherer Ebene“ ein Softwarebaustein (z.B. eine Klasse) gemeint, die von einem anderen Softwarebaustein (eine andere Klasse), dem sogenannten „Modul niedrigerer Ebene“, Dienste einfordert, also: an deren öffentlicher Schnittstelle (public API) Methoden aufrufen möchte.

Die in Bild 4 dargestellte prekäre Situation lässt sich also durch Anwendung des DIP, d.h. durch Einführung von Abstraktionen (Interfaces) wie in Bild 5 dargestellt, reparieren. Nun ist die Komponente X zwar noch von Y abhängig, aber Y nicht mehr von X. Alle angebotenen bzw. eingeforderten Dienste, die die Komponente Y aus ihrem Einsatzkontext benötigt, sind über Schnittstellen abstrahiert.

Abhängigkeiten injizieren mit Dependency Injection (DI)

Nun stellt sich natürlich noch die finale Frage: Wer definiert eigentlich, welche konkreten Objekte (Instanzen) im laufenden Programm an Stelle der nun eingeführten Interfaces zum Einsatz kommen? Nach dem Entwurfsprinzip Separation of Concerns sollte diese Zuständigkeit ja nicht bei den Objekten selbst liegen, denn dann wäre ja nichts gewonnen.

Schaut man sich Bild 5 an, dann ist durch Anwendung des DIP quasi so etwas wie eine flexible Plug-In-Schnittstelle entstanden. An Stelle der Klasse A aus der Komponente X könnte man problemlos auch eine beliebige andere Klasse (z.B. ein Mock-Objekt zum Testen) gegen die Interfaces „montieren“. Was wir jetzt noch benötigen ist eine Komponente, die quasi einen „Bauplan“ abarbeiten kann und dynamisch bei Programmstart und zur Laufzeit bestimmt, welche konkreten Instanzen erzeugt, und an den durch die Interfaces definierten Erweiterungspunkten eingesetzt werden.

In Abbildung 6 ist dieses Grundprinzip dargestellt. Die «component» Assembler erzeugt eine Instanz von Klasse B, und injiziert dieses Objekt über den Setter A::setService() in die zuvor erzeugte Instanz von A. Dieses ist möglich, da B kompatibel zu der erwarteten Schnittstelle Service ist. Dennoch weiß A nichts von der Existenz einer Klasse B; es gibt zwischen A und B keine Abhängigkeit.

Dieses Verfahren folgt einem Entwurfsmuster, welches Dependency Injection (DI) [2] genannt wird. Die Infrastruktur-Komponente, die für das Erzeugen der Instanzen und das „Injizieren“ selbiger verantwortlich ist („Assembler“ oder „Injector“ genannt), ist als Baustein der Infrastruktur entsprechend dem Separation of Concerns Prinzip getrennt von den Software-Bausteinen, die zur Erfüllung fachlicher Anforderungen zusammenarbeiten sollen (Alle Abhängigkeiten zeigen vom Assembler weg).

Störende Abhängigkeiten nicht einfach hinnehmen

Softwareentwickler und –architekten müssen störende Abhängigkeiten in einem Softwareentwurf nicht einfach als gegeben hinnehmen. Es gibt zahlreiche Möglichkeiten Abhängigkeiten so zu gestalten, dass sie wenig bis gar keine Schwierigkeiten bereiten.

Mit dem Dependency Inversion Principle (DIP) steht eine Maxime zur Verfügung, um Abhängigkeiten in die gewünschte Richtung zeigen zu lassen und auch eine lose Kopplung zu fördern. Ein Entwurf nach DIP ist auch die Voraussetzung, um die Auflösung der Abhängigkeiten zur Laufzeit durch das Dependency Injection (DI) Pattern zu ermöglichen. So erhält man letztendlich nicht nur einen hochgradig flexiblen und evolvierbaren Softwareentwurf. Darüber hinaus ist auch die Testbarkeit und Wiederverwendbarkeit der Bausteine ausgesprochen gut.

Der Autor

Stephan Roth, Trainer und Berater bei der oose Innovative Informatik eG in Hamburg.
Stephan Roth, Trainer und Berater bei der oose Innovative Informatik eG in Hamburg.
(Bild: oose Innovative Informatik eG)

*Stephan Roth ist Trainer und Berater bei der oose Innovative Informatik eG in Hamburg. Seine thematischen Schwerpunkte liegen in den Bereichen modellbasiertes Systems- und Software Engineering mit UML und SysML, Softwarearchitektur und -design, sowie Clean Code Development. Zudem ist er Autor international verlegter Fachbücher.

Quellen und Literatur

[1] David Lorge Parnas. On the Criteria To Be Used in Decomposing Systems into Modules. Communications of the ACM, Vol. 15, No. 12, 1972.

[2] Object Management Group. OMG Unified Modeling Language (OMG UML), Version 2.5. OMG Document Number: formal/2015-03-01. http://www.omg.org/spec/UML/2.5

[3] Martin Fowler. Inversion of Control Containers and the Dependency Injection pattern. January 2004. https://martinfowler.com/articles/injection.html.

(ID:45468332)