am 27. Mai 2010
Viele haben sicher schon einmal etwas von Inversion of Control (IoC) und Dependency Injection (DI) gehört. Und sicher auch, dass es ganz toll ist und es zu den best practices gehört. Nur eine Frage die oft nicht wirklich beantwortet wird: Wieso ist das so sinnvoll?
Ich versuche hier einmal diese Frage mit einem Beispiel aus dem realen Leben zu dokumentieren und zu beantworten…
Wir stellen uns eine größere mittelständige Firma vor. Sie hat mehrere Abteilungen die eigenständig arbeiten. Natürlich müssen da auch Dinge angeschafft werden und das kostet Geld. Entsprechend ihrem Bedarf bestellen diese Abteilungen also Dinge wie Drucker, Computer oder Spezialhardware. Die Rechnungen werden in die Buchhaltung gegeben und dann von dort aus bezahlt.
Das KANN alles super funktionieren. Aber nur dann, wenn diese Abteilungen auch verantwortungsbewusst mit Geld umgehen und nur Dinge anschaffen, die sie wirklich benötigen. Außerdem ist es schwer einen Abteilungsübergreifenden Überblick zu bekommen, welche Hardware vorhanden ist. Es kann auch passieren, dass mehrere Abteilungen die selbe Spezialhardware bestellen obwohl sie sich diese eigentlich hätten teilen können. Auch ist es schwer nachzuvollziehen wo denn das ganze Geld geblieben ist.
Wie kann man diesen Zustand also optimieren? So wie es in der Praxis oft gehandhabt wird: Die Abteilungen melden am Anfang eines Jahres ihren Bedarf an, daraus wird ein Gesamtbedarf ermittelt und ein Jahresbudget errechnet. Das wird dann angepasst und optimiert und jeder Abteilung ein Budget, welches sie zur Verfügung haben, mitgeteilt. Größere Anschaffungen müssen jedoch erst genehmigt werden (damit kann man doppelte Einkäufe teurer Spezialhardware vermeiden). Und da diese größeren Anschaffungen nur
noch von einer zentralen Stelle vorgenommen werden hat man darüber auch immer einen Überblick.
Und Dinge die sich so im Alltag bewährt haben machen auch beim Softwaredesign Sinn. Wieso soll sich eine Klasse Gedanken machen woher sie ihren Logger bekommt? Oder wo sie einen Zugang zur Datenbank findet? Das ist gar nicht ihre Aufgabe. Es ist viel sinnvoller einer Klasse einfach einen Logger oder eine Datenbankverbindung an die Hand zu geben und sie macht das was sie am besten kann. Ihre eigene Logik zu implementieren.
Soweit die graue Theorie. Ich hoffe ich habe nun alle (oder zumindest viele) von dem Nutzen der IOC und DI überzeugen können. Denn nun möchte ich zeigen, wie die Umsetzung dessen in der Praxis aussehen kann.
Hier der bisher unschöne Weg wie eine Klasse Ressourcen verwendet:
class MyClass { private $_logger; private $_dbHandle;
function __constructor() { $this->_logger = Zend_Registry::get('LOGGER'); $this->_dbHandle = Zend_Registry::get('DB'); } function doSomething() { // etwas loggen $this->_logger->write('foo');
// Daten aus der Datenbank lesen $this->_dbHandle->select('SELECT * FROM bar'); } }
Die Klasse holt sich im Konstruktor selber ihre Abhängigkeiten und verwendet sie dann.
Besser ist folgender Ansatz:
class MyClass { private $_logger; private $_dbHandle; function __constructor() { } function setLogger(Zend_Log_Writer_Abstract $logger) { $this->_logger = $logger; } function setDbHandle(Zend_Db_Adapter_Abstract $dbHandle) { $this->_dbHandle = $dbHandle; } function doSomething() { // etwas loggen $this->_logger->write('foo'); // Daten aus der Datenbank lesen $this->_dbHandle->select('SELECT * FROM bar'); } }
Die Klasse bekommt ihre Abhängigkeiten über Setter-Funktionen gesetzt. Man kann an dieser Stelle die Setter natürlich auch zugunsten von Konstruktor-Parametern ersetzen. Ein offensichtlicher Vorteil ist schon einmal, dass die Klasse sich nicht überlegen muss WOHER sie den Logger oder das DB-Handle bekommt. Der Zugriff über eine Registry ist nichts weiter als eine objektorientierte Form von globalen Variablen. Wenn sich der Zugriffsbezeichner ändert, müssen alle Klassen angepasst werden. Ebenso ist der Rückgabewert einer solchen Registry nicht Typsicher. Dieses Problem haben wir über die Setter-Funktionen gelöst.
Soweit möchte ich hier erst einmal Schluss machen für diesen Artikel. Im nächsten Artikel möchte ich dann einmal zeigen, wie man sehr einfach diese Dependency Injection umsetzen kann.