Dependency Inversion Principle

Hallo Spaß-Coder.

Zum Abschluss unserer Prinzipien-Reihe steht nun nicht mehr direkt ein Problem in einer Klasse im Zentrum, sondern vielmehr die Frage, wie sich Abhängigkeiten zwischen Klassen unterschiedlicher Ebenen verhalten. Wie also können wir darauf achten, dass unser System auch dann sauber bleibt, wenn es groß wird?

Hier hilft uns das Letzte Prinzip aus der Liste der SOLID-Prinzipien.

 

Dependency-Inversion-Prinzip (DIP)

„Module höherer Ebenen sollten nicht von Modulen niedrigerer Ebenen abhängen.
Beide sollten von Abstraktionen abhängen.

Abstraktionen sollten nicht von Details abhängen.
Details sollten von Abstraktionen abhängen.“

— Dependency-Inversion-Prinzip (Quelle: Wikipedia)

 

Erläuterung des Prinzips

DIP_AbhängikeitKlassenAnnahme: wir haben zwei Klassen in unterschiedlichen Paketen bzw. Namensräumen, von denen eine Klasse die andere Klasse kennt. Damit besteht zwischen den beiden Paketen eine Abhängigkeit. Ja und? Was ist das Problem? Mit einer starken Kopplung zwischen Paketen und generell zwischen Klassen nimmt die Austauschbarkeit ab. Nehmen wir als Beispiel einen CRUD-Service, der an eine bestimmte Implementierung einer Datenbankschicht gekoppelt ist. Was passiert, wenn die Datenbank gewechselt werden soll? Die Abhängigkeiten sind durch Änderung des Programmcodes aufzulösen, bevor eine neue Implementierung der Datenbankschicht angebunden werden kann. Was kann ich machen, um die Kopplung besser – also lose – zu gestalten?

 

DIP_AbhängikeitMitInterface Um Kopplungen durch Implementierungen auszulösen, können Schnittstellen eingeführt werden. Was für unser Beispiel dann wie links stehend aussehen könnte. Dabei zeigt das grüne Rechteck eine Schnittstelle (Interface), welche von der orangefarbene Klasse verwendet und von der blauen Klasse implementiert wird. Befinden sich orangefarbene Klasse und Schnittstelle in einem Paket, kann das drunter liegende Paket ausgetauscht werden, ohne in dem Paket darüber eine Änderung vornehmen zu müssen. Das neue Paket bringt einfach eine neue Implementierung der Schnittstelle mit.

In diesem Fall haben wir die Abhängigkeit umgedreht: bisher vom oberen Paket zum unteren, jetzt vom unteren Paket zum oberen.

 

DIP_CP Weiterhin können durch direkte Verwendung von Implementierungen, also Klassen, zirkuläre Abhängigkeiten entstehen, wie nebenstehende Grafik veranschaulicht. Diese Kreisabhängigkeiten kann der Compiler üblicherweise auflösen. Wenn allerdings ein Build-Server im Einsatz ist, kann dies bereits zum Problem führen. Dadurch, dass sich ein verwendetes Paket / Klasse ändert, muss das gerade betrachtete Paket / Klasse erneut erstellt werden, davon gibt es wieder eine Abhängigkeit, die erstellt werden muss – womit sich das System im Kreis dreht. Auch im Fall, dass die Implementierung angepasst werden muss, kann sich die Änderung durch alle Pakete / Klassen durchziehen. Wo fange ich an, wo höre ich auf?

 

 

 

DIP_CPUnterbrochenWie links gezeigt, lassen sich zirkuläre Abhängigkeiten auch durch das Einziehen von Schnittstellen aufbrechen. Der rote Pfeil im Bild deutet an, dass hier die Richtung der Abhängigkeit gedreht wurde. Damit ist die Kreisabhängigkeit unterbrochen.

Grundsätzlich ist das DIP nicht mit dependecy injection – also dem injizieren von Abhängigkeiten zu verwechseln. Ein inversion of control (IoC) Framgework wie z.B. Spring übernimmt dabei die Aufgabe, anhand der verwendeten Schnittstellen konkrete Implementierungen bereitzustellen. Grundvoraussetzung dafür ist, dass die Abhängigkeiten durch Schnittstellen bereits aufgelöst wurden.

Grundsätzlich haben wir mindestens im Open Closed Principle die lose Kopplung durch Schnittstellen besprochen.

 

Weiterhin erreichen wird mit dem DIP:

  • Unabhängigkeit zwischen Klassen und Modulen
  • Veränderbarkeit und Austauschbarkeit
  • Wiederverwendbarkeit

 

Unsere Erfahrung mit dem DIP

In einigen unserer Projekte haben wir ein IoC Framework verwendet (z.B. Ninject) und daher grundsätzlich mit Schnittstellen gearbeitet. Aber auch ohne Framework hat es sich als vorteilhaft erweisen, anstelle gegen eine Klasse, besser gegen eine Schnittstelle zu programmieren. Die Flexibilität ist deutlich höher. Zusätzlich hat sicherlich jeder mit Erfahrung in Unit Tests häufig festgestellt, dass sich Code mit Schnittstelle leichter testen lässt. Mocks und Stubs können einfacher eingeführt, die zu testende Implementierung leichter isoliert werden.

Battlez Server Architecture

 

Im Projekt Battlez haben wir eine übliche 3-Schichten Architektur verwendet (hier Netzwerk, Logik und UI, siehe Bild rechts) womit die verwendeten Bibliotheken für die Netzwerk-Kommunikation (wir nutzen LidgrenNetwork in einem eigenen Wrapper) sehr leicht auszutauschen sind – gekoppelt ist diese über nur zwei Schnittstellen.

Einen Hinweis möchte ich an dieser Stelle wiederholen: je mehr Komponenten ein Projekt enthält (schön klein, entkoppelt und wiederverwendbar), desto wichtiger wird die Projektstruktur sowie die Namensgebung. Aussagekräfte Namen (siehe auch Aussagekräftige Namen) unterstützen dabei, den gesuchten Code schnell wiederzufinden.

 

Zusammenfassung

Mit dem letzten Design Prinzip der SOLID-Prinzipien schließen wird diese Reihe ab. Das DIP unterstützt zusätzlich zu den bisherigen Prinzipien die Entkopplung von Komponenten und damit die Veränder- und Wiederverwendbarkeit. Die SOLID-Prinzipien sind in der genannten Reihenfolge anzuwenden und werden in konsequenter Anwendung den Programmcode positiv beeinflussen.

 

Viel Spaß beim Anwenden.

Eure Spaß-Coder.
Dieser Artikel basiert neben unseren Erfahrungen auf verschiedenen Internetquellen.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert