Wenn man etwas mit der objektorientierten Programmierung zu tun hat, ist man vielleicht schon mal auf den Begriff Dependency Injection (DI) gestoßen. Doch was ist das überhaupt und vor allem: Wie wende ich das ganze praktisch in meinem Code an?

Schauen wir uns zunächst die theoretische Bedeutung an: DI ist ein Pattern, welches ungewünschte Abhängigkeiten zwischen einzelnen Klassen vermeiden soll. Doch was bedeutet in diesem Zusammenhang ungewünscht? Um das zu veranschaulichen, schauen wir uns folgendes Beispiel an.

Stellen wir uns eine Feuerwehrwache vor. In der Feuerwehrwache gibt es den normalen Feuerwehrmann und den Gruppenführer. Der Gruppenführer gibt die Anweisungen, welche der Feuerwehrmann dann ausführt. Der Gruppenführer gibt beispielsweise den Einsatzauftrag „Rette die Katze aus dem Baum“! Der Feuerwehrmann möchte diesen Auftrag natürlich sofort ausführen, benötigt dafür allerdings eine Leiter. Nun gibt es folgende Möglichkeiten:

  • Leiter im Leitergeschäft des Vertrauens kaufen
  • Die Leiter vom Katzenbesitzer ausleihen
  • Die Leiter vom Fahrzeug des Maschinisten holen

Er muss sich also darum kümmern, die Leiter zu besorgen. Das ist aber eigentlich gar nicht seine Aufgabe. Er ist schließlich nur für die Durchführung der Rettung zuständig und nicht für die Auswahl der Geräte.

Übersetzt in Code sieht das Ganze dann so aus:

Klasse_Feuerwehrmann

Das Problem ist, dass wir die Beziehung zwischen Feuerwehrmann und Leiter nicht haben wollen. Viel schöner wäre es, wenn die Leiter schon von „außen“ übergeben wird. Wir lassen also den Gruppenführer entscheiden, welche Leiter eingesetzt werden soll. Die Erteilung des Auftrags könnte dann folgendermaßen aussehen:

Klasse_Gruppenfuehrer

Jetzt sollte man allerdings lieber alle Fenster in der Wohnung schließen damit einen die Softwarearchitekten aus dem Elfenbeinturm nicht aus dem Fenster schmeißen. Damit der Code an dieser Stelle kompilierfähig ist, muss das Attribut Leiter public sein. Und das geht natürlich gar nicht.

Um das Ganze ein bisschen schöner zu machen, setzen wir die Sichtbarkeit des Attributs auf private und setzen den Wert über eine Setter-Methode. Dadurch verhindern wir, dass von außen mit der Leiter irgendein Unfug gemacht wird.

Klasse_Gruppenfuehrer2

Nun haben wir aber ein Problem: Der Feuerwehrmann kann dann im Prinzip nur mit der Art von Leitern umgehen, welche vom Typ Leiter sind oder zumindest davon abgeleitet wurden. Andere Leitern wie zum Beispiel eine Drehleiter kennt er gar nicht.

Letztendlich haben wir dadurch nicht viele Vorteile. Der Gruppenführer ist jetzt für die Beschaffung zuständig und der Feuerwehrmann muss in der Lage sein, mit der Leiter umzugehen. Es sind also beide in irgendeiner Art und Weise von der Klasse Leiter abhängig. Wir können die Katze also immer noch nicht retten und langsam läuft uns die Zeit davon.

Wie wäre es, wenn wir dem Feuerwehrmann einfach sagen, dass er mit allen Rettungswerkzeugen umgehen kann? Wir sagen ihm nicht, ob er eine Steckleiter, eine Schiebeleiter oder eine Drehleiter bekommt sondern nur, dass er ein Werkzeug zum Retten erhält. Damit kommen wir zum Dependency-Inversion-Principle, welches eines von den SOLID-Prinzipien ist und folgendes aussagt:

„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.“

Damit wir dieses Prinzip erfüllen, brauchen wir zunächst ein Interface, welches wir IRettungswerkzeug nennen.

Interface_Rettungsgeraet

Nun können wir einfach jedem Rettungswerkzeug sagen, dass es das Interface IRettungswerkzeug implementieren soll und sind somit in der Lage, jedes Rettungsgerät an den Feuerwehrmann zu übergeben. Für die Leiter zur Katzenrettung könnte das dann folgendermaßen aussehen:

Klasse_Katzenleiter

Der Gruppenführer kann jetzt jedes erdenkliche Rettungswerkzeug, welches das Interface implementiert, an den Feuerwehrmann übergeben. Egal ob es für die Rettung einer Katze im Baum (Katzenleiter) oder zur Befreiung einer Person aus einem demolierten PKW (Rettungsschere) dient. Dem Feuerwehrmann ist die konkrete Implementierung egal. Die Hauptsache ist, dass er damit retten kann.

Klasse_Gruppenfuehrer2

Wird die Abhängigkeit im Konstruktor als Parameter reingegeben, sprechen wir von Konstruktor Injection. Rufen wir einen Setter auf, um einen Wert zu setzen den wir benötigen, dann sprechen wir von der Setter-Injection.

 Vor- und Nachteile

Das ganze da oben klingt alles total logisch. Aber was habe ich davon, DI anzuwenden? Nun, während der Entwicklung hat man erstmal nichts davon. Die Implementierungsphase wird sich zeitlich  in der Regel in die Länge ziehen. Um die Abhängigkeiten aufzulösen werden Interfaces und/oder noch weitere Klassen gebraucht. Außerdem wird der Code wesentlich abstrakter und damit zunächst komplizierter.

Allerdings werden wir auch mit Vorteilen belohnt: Durch die ganzen Abstraktionen wird tendenziell die Wiederverwendbarkeit gesteigert. Bei unserem Beispiel mit dem Feuerwehrmann bräuchte ich bei einer harten Abhängigkeit für jedes Rettungsgerät eine neue Methode, welche das passende Objekt erwartet. Durch DI kann ich nun ein und dieselbe Methode wiederverwenden da diese jedes Rettungsgerät annimmt.

Außerdem ist unser Code viel besser zu Testen. Um dies zu erläutern, schauen wir uns folgendes Beispiel an:

Einsätze sollen nach dem Beenden in eine Datenbank gespeichert werden. Zur Laufzeit steht uns eine Datenbank zur Verfügung, während der Testphase allerdings nicht. Bei jedem Test eine Datenbankverbindung aufzubauen, wäre viel zu viel Overhead. Gleichzeitig wäre unser Test von anderen Einflüssen wie zum Beispiel der Verbindung abhängig.

Es gibt also ein Interface ISpeicherbar und eine Klasse EinsatzDatenbank, welche das Interface implementiert und unseren Produktivcode enthält. Für unseren Test gibt es eine Klasse FakeEinsatzDatenbank, welche ebenfalls das Interface implementiert.

Gibt es nun eine Methode speichere(), dann würde unser Produktivcode in einer Datenbankverbindung den gesamten Einsatz ablegen. Die Implementierung unsere fake Datenbank gibt aber lediglich einen String wie zum Beispiel „Einsatz in der Datenbank abgelegt“ zurück. In einem Test kann der String jetzt total einfach abgefragt werden. Ein Framework, welches diese Funktionalität bereits mitbringt, ist das Mocking-Framework.

Fazit

Dependency Injection bedeutet eigentlich nichts anderes, als Abhängigkeiten zu entkoppeln. Die Klassen sollten möglichst keine Objekte, von denen sie abhängig sind, selbst erzeugen müssen, sondern nach Möglichkeit reinbekommen, was sie benötigen. Möchten wir jetzt etwas abstrakter werden, kommt das Dependency-Inversion-Principle dazu.  Es werden die konkreten Dinge abstrahiert und so eine hohe Wiederverwendbarkeit des Codes erreicht.

 

Wenn die mein Artikel gefallen hat oder du Verbesserungen hast, dann lass mir gerne ein Kommentar da oder schreib mir eine E-Mail! 🙂

Dependency Injection
Markiert in:             

Schreibe einen Kommentar

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

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.