© saicle/Shutterstock.com
Mit Dependency Injection Klassenabhängigkeiten kontrollieren

In and out of Control


Symfony2 ist vor Kurzem erschienen, die Flow3-Version 1.0 ist auf dem Markt und das Zend Framework 2 steht ebenfalls in den Startlöchern. Sie alle haben eine Gemeinsamkeit: Sie bringen einen Dependency-Injection-Container mit, um Testbarkeit zu erhöhen und Komplexität zu minimieren. Was genau hinter diesem Muster steckt und wie man es anwenden kann, erläutert dieser Artikel.

Ein guter Softwareentwickler macht es sich zur Aufgabe, implizite Abhängigkeiten in seinen Projekten zu minimieren. Das liegt daran, dass die Anzahl der abhängigen Klassen linear zum Testaufwand steht. Steht man Qualität und Stabilität seiner Applikation eher gleichgültig gegenüber, kann man seine Systeme weiterhin stark koppeln. Legt man jedoch Wert darauf, sollte man sich mit dem Dependency-Injection-Entwurfsmuster auseinandersetzen. In der Softwareentwicklung kommt man häufig an den Punkt, dass Klassen Abhängigkeiten zu anderen Klassen besitzen. In der klassischen Programmierung kümmert sich dabei jede Klasse selbst um ihre Abhängigkeiten und instanziiert benötigte Objekte oder nutzt Singletons in ihrem lokalen Kontrollfluss. Dass es sich bei diesem Vorgehen um ein potenzielles Problem handelt, veranschaulicht Listing 1. Diese oder ähnliche Zeilen Quellcode sind in jedem größeren Projekt zu finden. In einer Zeit, in der Testbarkeit noch keinen hohen Stellenwert im PHP-Umfeld hatte, war das eine gut funktionierende Lösung. Aus der Sicht der Qualitätssicherung sieht es aber ganz anders aus.

Listing 1

class A { private $db; public function __construct( ) { $this->db = new Db( ); } public function store( ) { $this->db->store($this->getData()); } ... } $a = new A();

Bei dem Versuch die Klasse A mit Unit Tests abzudecken, wird schnell klar, dass hier eine Abhängigkeit aufgebaut wurde, die nicht so einfach von außen aufzulösen oder zu ersetzen ist. Für diesen Fall bedeutet das konkret, dass jedes Mal, wenn Klasse A getestet werden soll, eine reale Datenbank benötigt wird. Das ist nicht sehr aufwendig und kostspielig, sondern eigentlich sogar unnötig: Dass die Anbindung funktioniert, wird eigentlich in den Unit Tets für die Datenbank verifiziert und nicht an dieser Stelle. Das scheint im Kontext einer kleinen Webanwendung noch akzeptabel, steigt jedoch die Komplexität der Anwendung, steigt auch der Aufwand des Testens entsprechend. Damit es unter Projektdruck dann nicht heißt, dass man ab sofort auf Tests verzichten muss, sollte man es gar nicht erst so weit kommen lassen und diese Abhängigkeiten bereits im Vorfeld aus dem Weg räumen.

Ein weiterer großer Nachteil beim Aufbau von Software auf die oben beschriebene Weise ist die Art der Abhängigkeiten: Sie sind implizit und von außen nicht zu sehen. Das Information-Hiding-Prinzip [1] klingt zunächst positiv. Soll das System aber verständlich und auf sauberer Architektur aufgebaut sein, die auf Redundanz und lose Kopplung fußt, so führt der Weg über das Geheimnisprinzip schnell in eine Sackgasse: Systemweit Datenbanken auszutauschen wird in einem solchen Fall zur Herausforderung. An dieser Stelle dienten Singletons einige Zeit als Lösung, da sie eine globale Variable zur Verfügung stellen, die von überall genutzt werden kann, bestehen bleibt dabei dennoch das Testbarkeitsproblem, da die Abhängigkeiten immer noch hart verdrahtet sind.

Ein Lösungsansatz ist Dependency Injection (DI): Abhängigkeiten werden explizit gemacht, die Testbarkeit hergestellt, und dabei ist Dependency Injection in ihrer Reinform trotzdem einfach zu verstehen und umzusetzen. Eine wichtige Idee hinter dem DI-Entwurfsmuster entspringt dem Inversion-of-Control-Konzept [2]. Das bedeutet, dass die verwendeten Klassen sich nicht mehr selbst um Ablauf und Abhängigkeiten kümmern, sondern dass dies ein Framework (nicht im Sinne von Webframework) übernimmt. Die Klasse gibt sozusagen die Kontrolle ab und lässt sich von außen steuern. Oft wird die DI mit Inversion of Control (IOC) gleichgesetzt, wobei sie aber genaugenommen nur eine mögliche Implementierung von IOC ist. Wie die Abhängigkeiten in dem oben erwähnten Beispiel herausgelöst werden können, wird anhand des neuen, in Listing 2 dargestellten, Codes deutlich.

Listing 2

class A { private $db; public function __construct(Database $db) { $this->db = $db; } public function store( ) { $this->db->store($this->getData()); } ... } $a = new A(new Db());

Werden die Abhängigkeiten über den Konstruktor injiziert, spricht man von einer Constructor Injection. Eine oft vertretene Meinung ist, dass der Konstruktor möglichst dumm sein soll, daher entwerfen wir hier auch eine sauberere Lösung: Die Constructor Injection sollte immer dann verwendet werden, wenn die benötigten Objekte nicht optional sind, damit sie nicht durch Bedienfehler vergessen werden können. Optionale Abhängigkeiten, wie zum Beispiel ein Logging-System, das nur bei Bedarf instanziiert werden muss, werden ü...

Neugierig geworden? Wir haben diese Angebote für dich:

Angebote für Gewinner-Teams

Wir bieten Lizenz-Lösungen für Teams jeder Größe: Finden Sie heraus, welche Lösung am besten zu Ihnen passt.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang