Inversion of Control

Posted by in Pattern, PHP

Heute ist mein Artikel Inversion of Control auf PHP hates me erschienen. Danke nochmal an Nils, dass er mir diese Plattform geboten hat um meine Artikel auch einem größeren Publikum zugänglich zu machen. Ansonsten als Futter für die Suchmaschine hier nochmal der komplette Artikel.

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 und 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.