Das Grundkonzept der Inversion of Control wird aktuell sehr oft durch eine Dependency Injection Implementierung erfüllt. HIerbei wird zumeist auf DI-Container zurückgegriffen. Es ist aber vom Konzept nicht unbedingt notwendig und daher gibt es im Teil 1 der Artikelserie ein Beispiel für eine Art Dependency Injection zu nutzen ohne auf einen DI-Container zurück zugreifen.
Zuerst muss man das Problem klar machen, das Dependency Injection lösen möchte. Man sollte ein Konzept oder Pattern nicht benutzen weil es gerade cool ist, sondern weil ein Problem besteht. Zumindest ist diese Vorgehensweise mE sinnvoll und führt nicht zu einem Pattern-Zoo.
Angenommen man hat eine Klasse und in dieser wird auf eine DB zugegriffen. So möchte man nicht jedem Aufruf der Klasse eine neue Datenbankverbindung öffnen, sondern auf eine bestehende Verbindung zurückgreifen. Ein ganz beliebtes Pattern hierfür ist das Singleton. Leider ist man hier in einem Testfall ziemlich aufgeschmissen, da man eine DB benötigt, um den Unittest ausführen zu können. Man möchte lieber ein Mock-Objekt nutzen. Nur wie kann man das Datenbank-Objekt durch ein Mock-Objekt ersetzen. Und genau hier kommt die Dependency Injection ins Spiel. Das Datenbank-Objekt ist eine Dependency und diese stecken wir nun irgendwie in die Beispielklasse.
class Example { public function getSomething() { return secretFunction(Database::get()->query("Select * from test")); } }
Constructor Injection:
Die erste Idee ist die Konstruktor Injection. Dabei erweitert man den Konstruktor der Beispielklasse um ein Parameter, über das wir dann ein Datenbankobjekt in die Beispielklasse stecken können. Die Beispielklasse kann dann auf einem Klassenattribut arbeiten. Im Unittest funktioniert das Spiel genauso, jedoch wird die Beispielklasse dann mit einem Mock-Objekt initialisiert. Somit haben wir die beiden Klassen entkoppelt und die Testbarkeit des Gesamtcodes erhöht.
class Example { private $db; public function __construct(MyDatabase $db) { $this->db = $db; } public function getSomething() { return secretFunction($this->db->query("Select * from test")); } }
Nachteile:
Ein Problem bei der Constructor Injection sind Klassen, die ein paar mehr Abhängigkeiten haben. Dann wird der Aufruf des Konstruktors aufgebläht und schnell unübersichtlich. Weiter muss die Datenbank immer weitergereicht werden. Sodass teilweise Klassen die Datenbank kennen, obwohl sie diese eigentlich gar nicht benötigen. Wenn die Beispielklasse aber initialisiert werden soll, muss die Datenbank eben vorhanden sein.