Stefan Lieser

Stefan Lieser

DIP oder IOSP

Vom Dependency Inversion Principle (DIP) zum Integration Operation Segregation Principle (IOSP)

 

Für viele Entwickler ist das Dependency Inversion Principle (DIP) eines der wichtigsten Prinzipien. Schließlich ist es Bestandteil der SOLID Prinzipien, da muss es doch wichtig sein. Doch ist das DIP tatsächlich so wichtig? Oder gibt es Alternativen, die zu noch besseren Ergebnissen führen? Diese und ähnliche Fragen soll der folgende Beitrag beantworten.

Bevor wir uns der Frage zuwenden, ob das Dependency Inversion Principle durch ein anderes Prinzip ergänzt oder sogar in Teilen abgelöst werden kann, müssen wir klären, wie es überhaupt zum DIP gekommen ist. Thema des DIP sind die Abhängigkeiten, engl. Dependencies. Die folgende Abbildung zeigt eine Abhängigkeit zwischen zwei Funktionseinheiten.

vom Dependency Inversion Principle zum Integration Operation Segregation Principle - Clean Code Developer Akademie - Trainings - Abb. 1
Abbildung 1: Abhängigkeit zwischen zwei Funktionseinheiten

Abhängigkeit zwischen zwei Funktionseinheiten

 

Auf einer zunächst rein technischen Ebene betrachten wir die Richtung dieser Abhängigkeit als falsch herum. Daher ist der Abhängigkeitspfeil in rot gezeigt. Falsch herum bedeutet hier, dass die Richtung besser von unten nach oben verlaufen sollte, statt von oben nach unten.

Es könnte bspw. bedeuten, dass die Richtung nicht von der Domänenlogik zur Datenbank verlaufen sollte, sonders andersherum. Zunächst spielt es keine Rolle, aus welchem Grund die Richtung umgekehrt werden soll. Das Dependency Inversion Principle schlägt eine Lösung vor, wie die Umkehr der Richtung, rein technisch gesehen, erfolgen kann. Siehe dazu die Abbildung 2.

Kontrakt zwischen A und B

 

Hier ist zwischen A und B ein Kontrakt für B, genannt BC, eingeschoben. A ist nun nicht mehr unmittelbar von B abhängig, sondern ist stattdessen von BC, dem Kontrakt für B abhängig. Da B den Kontrakt implementiert, ist auch B von BC abhängig. Und genau daraus ergibt sich nun die Umkehr der Abhängigkeitsrichtung.

Sie verläuft nun von unten nach oben, statt wie in Abbildung 1 von oben nach unten. Nun könnte man einwenden, dass es nur einer anderen Darstellung bedarf, um wieder eine Abhängigkeit von oben nach unten zu haben. Abbildung 3 zeigt diese Überlegung.

vom Dependency Inversion Principle zum Integration Operation Segregation Principle - Kontrakt zwischen A und B - Abb. 2
Abbildung 2: Kontrakt zwischen A und B
vom Dependency Inversion Principle zum Integration Operation Segregation Principle - vertikale Anordnung der Funktionseinheiten - Abb. 3
Abbildung 3: Vertikale Anordnung der Funktionseinheiten

Vertikale Anordnung der Funktionseinheiten

 

Jetzt sehen wir erneut eine Abhängigkeit von oben nach unten, somit also wieder in der ungewollten, falschen Richtung. Im Unterschied zur Abbildung 1 ist das konkrete A nun allerdings nicht von etwas Konkretem abhängig, sondern vom abstrakten Kontrakt. Insofern ist die Abhängigkeit von A nach BC schwächer als die ursprüngliche, von A nach B.

Diese Form der Richtungsumkehr, durch die Indirektion über einen Kontrakt, wird als Dependency Inversion bezeichnet und ist die Kernaussage des Dependency Inversion Principles (DIP).

Vorteile

 

Schauen wir uns an, zu welchen Vorteilen dies führt. Dadurch, dass A nicht mehr vom konkreten B abhängig ist, sondern von BC, dem Kontrakt für B, sind wir in der Lage, die Abhängigkeit auf unterschiedliche Weise zu erfüllen. Zum einen kann der Kontrakt in automatisierten Tests durch eine Attrappe bereitgestellt werden, zum anderen können unterschiedliche Varianten der Implementation des Kontrakts bereitgestellt werden.

Testen mit Attrappen

 

Setzen wir beim automatisierten Testen eine Attrappe anstelle des „echten“ B ein, sind wir dadurch in der Lage, das konkrete A isoliert zu testen. Ohne die Möglichkeit der Attrappe, siehe Abbildung 1, bleibt nur der Integrationstest von A inkl. B. Technisch gesehen benötigen wir für den Test mit Attrappen zusätzlich das Konzept der Dependency Injection (DI). Damit versetzen wir uns in die Lage, zur Laufzeit zu entscheiden, welche konkrete Implementation des Kontrakts BC von der Funktionseinheit A verwendet werden soll. Ohne DIP und ohne DI sind wir nicht in der Lage, die Strukturbeziehung zwischen A und B zur Laufzeit zu beeinflussen. A verwendet B unmittelbar. Durch das DIP in Verbindung mit DI können wir zur Laufzeit entscheiden, ob wir A ein B bereitstellen, oder irgendeine andere Funktionseinheit, die sich an den Kontrakt BC hält. Aus Sicht von A macht das keinen Unterschied, da A kein B erwartet sondern ein BC. Im automatisierten Test können wir daher eine Attrappe von B an A übergeben. Damit sind wir in der Lage, A zu testen, ohne dass das echte B involviert ist.

 

Austauschbarkeit der Implementation

 

Der zweite Nutzen aus der Dependency Inversion entsteht dadurch, dass jetzt unterschiedliche Varianten einer Implementation von BC verwendet werden können. Auf diese Weise sind wir bspw. in der Lage, das Speichern von Daten einmal mit einem SQL Server zu realisieren und das andere mal mit einer CSV Datei. Solange beide Implementationen dem Kontrakt BC genügen, können sie dem Verwender injiziert werden, ohne dass dieser einen Unterschied feststellen könnte. Dieser zweite Vorteil, der aus DIP und DI erwächst, ist tatsächlich einer, der im Folgenden durch das IOSP nicht in Frage gestellt wird. Allerdings erfolgt ein solcher Austausch einer Implementation in der Praxis eher selten. Insofern ergibt es wenig Sinn, pauschal immer mit Interfaces zu arbeiten, nur um die Austauschbarkeit vorzusehen, die dann praktisch kaum verwendet wird.  Schauen wir uns nun an, ob es eine Alternative zum Dependency Inversion Principle geben kann.

 

Der Aspekt der Integration

 

Ausgangspunkt der Überlegung ist dabei die Tatsache, dass in A zwei Aspekte vermischt sind. A ist einerseits dafür zuständig, seine offensichtliche Aufgabe zu erfüllen: A enthält Logik. In der Regel lässt sich diese Aufgabe aus dem Namen erkennen. Dass in A Logik enthalten sein muss, liegt auf der Hand. Eine Funktionseinheit A, die lediglich eine Methode aus B aufruft und sonst keine eigene Logik enthält, ergibt keinen Sinn.  Aufgrund der Abhängigkeit zu B verfügt A somit aber noch über eine weitere Zuständigkeit: die Integration von B. A verwendet B, sonst wäre A nicht von B abhängig. Irgendetwas macht A offensichtlich mit B. Technisch gesehen ruft A bspw. eine Methode aus B auf. Dieser Aufruf bedeutet, dass hier eine Integration stattfindet. Bei B ist dies anders. B hat keine Abhängigkeiten, ruft niemanden auf, integriert somit keine andere Funktionseinheit. Diese Tätigkeit bezeichnen wir als Operation. Und nun kommt der springende Punkt: in A sind die beiden Aspekte Integration und Operation vermischt.

Methodenaufrufe

 

Um die beiden Kategorien Integration und Operation unterscheiden zu können, müssen wir differenzieren, welche anderen Methoden eine Methode aufruft. Ruft eine Methode nur eigene Methoden auf, also solche, die ebenfalls zur Lösung gehören, sprechen wir von Integration. „Zur Lösung gehören“ meint hier, dass die Methoden zum gleichen Softwaresystem gehören bzw. von den gleichen Entwicklern bearbeitet werden, wie die integrierende Methode. Anders verhält es sich mit dem Aufruf von Methoden aus Frameworks oder der Runtime. Methoden, die Framework oder Runtime Methoden aufrufen, bezeichnen wir als Operation. Ferner werden in Operationen auch Ausdrücke verwendet wie bspw. „x > 42“ oder „y + 1“. Somit ergibt sich eine sehr einfache Regel für die Unterscheidung:

In einer Integration ist folgendes erlaubt:
• Aufruf anderer Methoden, die ebenfalls zur Lösung gehören

In einer Operation ist folgendes erlaubt:
• Aufruf von Methoden aus Frameworks oder der Runtime
• Ausdrücke

Diese Kriterien sind ausschließend. D.h. anhand dieser Regeln können wir sehr einfach erkennen, ob eine Methode für den Aspekt der Integration oder Operation zuständig ist. Ferner lässt es sich somit auch leicht erkennen, ob in einer Methode beide Aspekte vermischt sind.

Das IOSP

 

In Abbildung 1 sind die beiden Aspekte Integration und Operation vermischt. Das wird erkennbar, weil A ganz offensichtlich B verwendet. Dies ergibt sich aus der Abhängigkeit. Ferner ist es naheliegend, dass A auch für den Aspekt der Operation verantwortlich ist. Würde A keinen Operationsanteil enthalten, wäre die Abhängigkeit von A zu B wenig sinnvoll, da A ausschließlich von B abhängt und somit nicht dafür verantwortlich ist, diverse Abhängigkeiten zu bedienen. Welche Schlussfolgerungen können wir nun daraus ziehen? Wir können die Aufgabenstellung, die in Abbildung 1 durch eine Abhängigkeit von A nach B gelöst wird, auch auf andere Weise lösen. Abbildung 4 zeigt, wie das gehen kann.

Integration als eigenständiger Aspekt

Nun ist eine weitere Funktionseinheit ergänzt, deren Aufgabe es ist, die Integration von A und B vorzunehmen. A und B sind jeweils Operationen. Das bedeutet für A eine wesentliche Änderung: A ist nun befreit vom Aspekt der Integration. Und damit kommen wir zum springenden Punkt: es muss nun keine Abhängigkeitsrichtung mehr umgedreht werden.

Auch ein Injizieren ist nicht mehr erforderlich. Das liegt daran, dass die Funktionseinheit I ausschließlich für die Integration zuständig ist und A sowie B freigestellt sind von Abhängigkeiten. Dadurch lassen sich beide automatisiert Testen, ohne dass mit Attrappen gearbeitet werden muss.

Integration Operation Segregation Principle (IOSP)
Abbildung 4: Integration als eigenständiger Aspekt

Fazit

 

Wenn wir konsequent zwischen Integration und Operation unterscheiden, reduzieren wir damit die Notwendigkeit von DIP und DI auf ein Minimum. Für die Testbarkeit wird das DIP dann nicht mehr benötigt. Somit bleibt nur die Aufgabe, mithilfe von DIP und DI alternative Implementationen zu unterstützen. Das ist, wie bereits beschrieben, allerdings nur selten erforderlich. Daher können wir schlussfolgern: DIP und DI können für die Testbarkeit vollständig durch das IOSP ersetzt werden. Es bleibt lediglich die seltene Verwendung für alternative Implementationen übrig.

Sowohl das Dependency Inversion Principle (DIP) als auch das Integration Operation Principle (IOSP) gehören zu den Prinzipien der Clean Code Developer Initiative.

Wenn Sie detaillierte Informationen zum Umgang mit DIP und IOSP benötigen oder lernen möchten, wie eine Codebasis davon profitieren kann, buchen Sie am besten eines unserer Clean Code Seminare.

Unsere Seminare

course
Clean Code Developer Basics

Prinzipien und Tests – Das Seminar wendet sich an Softwareentwickler, die gerade beginnen, sich mit dem Thema Softwarequalität auseinanderzusetzen. Es werden die wichtigsten Prinzipien und Praktiken der Clean Code Developer Initiative vermittelt.

zum Seminar »
course
Clean Code Developer Advanced

Mit Flow Design von den Anforderungen zum Clean Code – Lernen Sie mit Flow Design einen Softwareentwicklungsprozess kennen, der Sie flüssig von den Anforderungen zum Clean Code führt.

zum Seminar »
course
Clean Code Developer Trainer

Seminare als Trainer durchführen – Dieses Seminar wendet sich an Softwareentwickler, die ihr Wissen über die Clean Code Developer Prinzipien und Praktiken bzw. über Flow Design als Trainer an andere weitergeben möchten.

zum Seminar »
course
Clean Code Developer CoWorking

Online CoWorking inkl. Coaching –
Wir werden häufig gefragt, was man als Entwickler tun könne, um kontinuierlich dran zu bleiben am Thema Clean Code Developer. Unsere Antwort: Treffen Sie sich regelmäßig wöchentlich online mit anderen Clean Code Developern.

zum Seminar »

4 Kommentare zu „DIP oder IOSP“

  1. David Mann

    Vielen Dank für diesen Artikel. Wir arbeiten hier an einem Produkt mittels DIP mit DI und folgen unbewusst weitestgehend auch dem IOSP, einfach aufgrund der Übersichtlichkeit und einfacheren Testbarkeit.

    Was ich nicht ganz verstehe ist, warum bei IOSP weitestgehend auf DI verzichtet werden kann.
    Wenn Code so flexibel sein soll, dass die meisten Operationen je Einsatzzweck austauschbar sein sollen, so ist das m.E. nur über Interfaces machbar.

    1. Hallo und vielen Dank für die Rückfrage.
      in der Tat ist bei alternativen Implementationen DIP/DI die Lösung. Die Frage ist allerdings, wie häufig das tatsächlich vorkommt. Meiner Beobachtung nach recht selten. Ich meine, man sollte nur dort Interfaces einziehen, wo tatsächlich mit mehr als einer Implementation gearbeitet wird. Nicht auf Vorrat, sondern erst bei konkretem Bedarf.

  2. Vielen Dank für die zeitnahe Beantwortung meiner Frage.
    Da eines der größten USP unseres Produktes gegenüber der Konkurrenz die Anpassungsfähigkeit an Kundenanforderungen ist, betrifft das doch einen großen Teil operationaler Funktionen.
    Dennoch halte ich den Ansatz von IOSP für sehr gut. Wir werden das künftig mal „bewusst“ verfolgen. 🙂

Kommentar verfassen

Deine E-Mail-Adresse wird nicht veröffentlicht.