SOLID: Dependency Inversion Principle

Das Dependency Inversion Principle ist das letztgenannte der fünf Prinzipien für gutes Software-Design gemäss SOLID. Dies soll jedoch nicht heissen, dass es das am wenigsten Wichtige der fünf ist, oder dass mit dass den zahlreichen Prinzipien neben SOLID (DRY, YAGNI, KISS...) keine Beachtung geschenkt werden soll!

Der Link zu den anderen SOLID Prinzipien

Die SOLID-Prinzipien verlangen, dass der Quellcode gewisse Eigenschaften und Strukturen aufweist, geben jedoch keine Vorgaben, wie diese Strukturen umzusetzen sind.

  • Das Single Responsibility Principle verlangt, dass jede Klasse nur eine Aufgabe erfüllt, und damit alle anderen Aufgaben an andere Klassen abgegeben werden sollen. Aber wie kann dann gewährleistet werden, dass die Klassen nach wie vor zusammenarbeiten?
  • Das Open/Closed-Principle schreibt vor, dass Quellcode nicht mehr verändert werden darf, nachdem er einmal produktiv eingesetzt wurde. Aber es ist nur natürlich, dass sich die Anforderungen an eine Applikation ständig ändern. Wie kann diesen Anforderungen gerecht werden, ohne das OCP zu verletzen?
  • Das Liskov'sche Substitutionsprinzip hat zum Ziel, dass möglichst jede Klasse durch eine andere Implementation ausgetauscht werden kann, ohne dass die umliegenden Klassen verändert werden müssen. Aber wie ist das möglich, wenn die neue Klasse einen anderen Namen hat als die alte?

Alle diese Fragen und Herausforderungen können mit dem Dependency Inversion Principle gelöst werden! Viel mehr kann sogar gesagt werden, dass das Dependency Inversion Principle eine Implikation des OCP und LSP ist, und man den anderen Prinzipien nicht folgen kann, ohne auch dem DIP zu folgen.

Das Prinzip

Das Prinzip wurde 1996 von Robert C. Martin beschrieben und mit dem Namen "Dependency Inversion Principle" versehen. Die Kernaussagen sind:

 

A. High level modules should not depend upon low level modules. Both should depend upon abstractions.

 

 

B. Abstractions should not depend upon details. Details should depend upon abstractions.

Robert C. Martin, "The Dependency Inversion Principle", 1996

Um diese Aussagen richtig zu verstehen, ist es nötig, die verwendeten Begriffe in diesem Zusammenhang genau zu beschreiben:

  • Modules: Entspricht Klassen in den meisten OO-Sprachen.
  • High-level / low-level modules: "Höher" im Sinne von "näher am User Interface". Jedoch können auch Logik-Klassen, also etwa Controller bei der MVC-Architektur zu den High-level modules gezählt werden. Low-level modules sind hauptsächlich Klassen ausserhalb der Applikations-spezifischen Klassen, also Libraries und Framework-Klassen.
  • Depend: Eine Klasse hat eine Abhängigkeit, wenn sie eine andere Klasse braucht, um zu funktionieren.
  • Abstractions: Abstraktionen sind Beschreibungen der Aufgaben bzw. Funktionen einer Klasse gegen aussen. Als Metapher: Die Abstraktion eines Zuges aus Sicht des Fahrgastes ist, dass er fährt. Wie es fertig gebracht wird, dass er fährt ist nicht Teil der Abstraktion.
    In der Praxis werden Abstraktionen mit Interfaces umgesetzt.
  • Details: In diesem Kontext werden unter Details alle Eigenschaften einer Klasse verstanden, welche sich ändern können, ohne dass sich die Abstraktion ändert. Für einen Pendler ist es irrelevant, ob sein Zug nun mit Strom oder Diesel betrieben wird, somit ist das für ihn ein Detail.

In seinem Artikel beschreibt Robert C. Martin drei Eigenschaften, welche zu schlechter Software-Qualität führen können, und mit dem Dependency Inversion Principle vermieden werden können:

  1. Änderungen an einer Klasse ziehen Anpassungen an vielen anderen Klassen nach sich ("Rigidity").
  2. Änderungen führen zu unerwarteten neuen Fehlern an anderen Stellen in der Applikation ("Fragility").
  3. Komponenten können nicht wiederverwendet werden, weil sie zu stark von anderen Komponenten abhängig sind ("Immobility").

Layering mit und ohne DIP

Die Bezeichnung "Dependency Inversion Principle" impliziert, dass ein Vergleich gezogen wird zu einer anderen Art, Software zu strukturieren, und diese Struktur dann invertiert wird.

 

Der Vergleich wird gezogen zu "traditionellen" Software-Strukturen. Bei diesen wird die Applikation schrittweise von den Low-level Klassen (z.B. das Framework) zu den High-level Klassen (Logik und UI) auf den darunterliegenden Modulen aufgebaut, und damit Abhängigkeiten erstellt. Dies hat zur Folge, dass sich eine Änderung in den Low-level Modulen durch die gesamte Applikation ziehen würde --> Abhängigkeiten sind transitiv. Bei einer MVC-Applikation könnte das Layering also so aussehen:

Mit dem Dependency Inversion Principle hingegen würden die Layer schematisch so aussehen:

Das Dependency Inversion Principle kann aber sehr wohl auch innerhalb eines Layers zur Verwendung kommen, insbesondere bei Micro-Service-Architekturen.