Dependency Injection – Beispiel in PHP

Begriffe die man öfters im Zusammenhang mit gutem Softwaredesign hört, ist Inversion of Control und Dependency Injection (DI). Aber was ist das eigentlich? Dependency Injection ist eine Möglichkeit um Inversion of Control zu erreichen – toll und jetzt? Am besten schaut man sich ein Beispiel an:

Beispiel ohne Dependency Injection

Angenommen wir haben eine Klasse MyApp, die für das Logging eine weitere Klasse Logger verwendet. Ganz simpel könnte das ohne Dependency Injection so aussehen:

class MyApp {
	private $obj_logger;

	public function __construct($logger_type) {
		//Problematische Stelle:
		//Hier wird ein Objekt der Klasse Logger
		//im Konstruktor von MyApp erstellt
		$this->obj_logger = new Logger($logger_type);
	}

	public function doSomething() {
		$this->obj_logger->log("foobar something");
	}
}

class Logger {
	private $level;
	
	public function __construct($level) {
	$this->level = $level;
	}
	
	public function log($what){
		echo "Logger Level: ".$this->level." says: ".$what;
	}
}

//Programmausführung
$app = new MyApp("Normal");
$app->doSomething();

Was ist das Problem?
Die Klasse MyApp ist abhängig von der Klasse Logger. Wird der Kontruktor von Logger irgendwann angepasst (z.B. neuer Parameter hinzugefügt), muss man die Klasse MyApp auch anpassen, da das Argument $logger_type vom Konstruktor von MyApp an den Kontruktor von Logger durchgereicht wird.
Generell ist das eigentlich ja auch unlogisch. Warum sollte man zur Erzeugung eines MyApp Objekts einen Logger-Typ mit angeben? Das hat ja nichts miteinander zu tun.
Wie geht es also besser?

Beispiel mit Dependency Injection

class MyApp {
	private $obj_logger;

	//Besser:
	//Objekt der Klasse Logger wird bei der
	//Instanziierung von MyApp als Parameter mit angegeben.
	public function __construct(Logger $obj_logger) {
		$this->obj_logger = $obj_logger;
	}

	public function doSomething() {
		$this->obj_logger->log("foobar something");
	}
}

class Logger {
	private $level;
	
	public function __construct($level) {
		$this->level = $level;
	}
	
	public function log($what){
		echo "Logger Level: ".$this->level." says: ".$what;
	}
}

//Programmausführung
//Hier muss nun ein Logger-Object erstellt werden
//und dem Konstruktor von MyApp mitgegeben werden.
$l = new Logger("Normal");
$app = new MyApp($l);
$app->doSomething();

Jetzt wird im Kontruktor von MyApp kein Logger-Objekt mehr erstellt. Dies geschieht nun bei der Programmausführung (oder auch Laufzeit). Dadurch entsteht keine so starke Abhängigkeit mehr zwischen den Klassen. Wenn man dann noch aus Logger ein Interface erstellt wird die Abhängigkeit noch weiter verringert, da man dann verschiedene Logger in MyApp initiieren kann.

//Statt Klasse Logger Interface
interface Logger {
	public function log($what);
}

class MyApp {
	private $obj_logger;
	
	//Angabe des Interface statt Klasse im Konstruktor
	public function __construct(Logger $obj_logger) {
		$this->obj_logger = $obj_logger;
	}

	public function doSomething() {
		$this->obj_logger->log("foobar something");
	}
}

class SimpleLogger implements Logger {
	private $level;
	
	public function __construct($level) {
		$this->level = $level;
	}
	
	public function log($what){
		echo "SimpleLogger Level: ".$this->level." says: ".$what;
	}
}
//Programmausführung
//SimpleLogger erzeugen
$l = new SimpleLogger("Normal");
$app = new MyApp($l);
$app->doSomething();

Das Verfahren nennt sich übrigens Constructor Injection. Als Merkregel könnte man auch sagen: „Keine Objektinstanziierung einer anderen Klasse im Konstruktor“.
Neben der Constructor Injection gibt es noch Setter Injection und Interface Injection.
Oft hat man auch von Dependency Injection Frameworks oder Komponenten gehört. Was machen diese? Wie wir vorhin gesehen haben, muss man zur Programmausführung erst einmal eine Reihe von Objekten erzeugen und diese ineinander stecken (in unserem Beispiel nur zwei, aber das können schnell viele werden). Die DI-Frameworks nehmen einem diese Arbeit ab, indem man einmal die Objekterzeugung und Verknüpfung untereinander in irgendeiner Konfigurationsdatei anlegt und das Framework dann alle Objekte erstellt und verknüpft. Dies hilft vor allem auch bei automatisierten Unit-Tests.

Gute Tutorials gibt es auch bei tuts+ oder bei Wikibooks (Dieser Beitrag basiert weitestgehend auf diesem)

Aggregation, Komposition und DI

Wenn man mit UML arbeitet, verwendet man bzgl. Abhängigkeiten Komposition (ausgefüllter Diamant mit Linie) und Aggregation (nicht ausgefüllter Diamant mit Linie). Hierdurch werden Teil-Ganzes Beziehungen abgebildet. Eine Komposition ist die stärkere Aggregation. Die Verbindung zwischen den Teil(en) und dem Ganzen wird als untrennbar definiert.
Aggregation und Komposition lassen sich auch mit Dependency Injection in Verbindung setzen. Komposition kann man als enge Kopplung sehen. Aggregation als lose Kopplung und somit als Representation der DI.
Aggregation, Komposition DI auf Stackoverflow



Als erster einen Kommentar schreiben.

Schreibe einen Kommentar

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