50
Dependency Injection Eine Einführung mit grundlegenden Beispielen (c) Carsten Hetzel, 2014

Dependency Injection - A practical introduction

Embed Size (px)

Citation preview

Page 1: Dependency Injection - A practical introduction

Dependency InjectionEine Einführung mit grundlegenden Beispielen(c

) C

ars

ten

He

tze

l, 2

014

Page 2: Dependency Injection - A practical introduction

Was ist eigentlich das Problem?

(c)

Ca

rste

n H

etz

el,

20

14

Page 3: Dependency Injection - A practical introduction

Was ist eigentlich das Problem?

Wie üblich bei gängigen Prinzipien aus der Softwareentwicklung liegt dem Prinzip ein wiederkehrendes Problem zugrunde.

Schauen wir uns dazu folgende Klasse an:

(c) Carsten Hetzel, 20143

Page 4: Dependency Injection - A practical introduction

class UglyCoupling{

private $handle;

public function __construct(){

$filename = 'output.txt';$handle = @fopen($filename, 'w');if (!$handle) {

throw new \RuntimeException('Unable to open output file!');}$this->handle = $handle;

}

public function updateOrder($orderId, $newValue){

// ... update the order$output = 'The value of order ' . $orderId . ' has been changed to ' . $newValue . '!';$bytes = fwrite($this->handle, $output);if ($bytes === false) {

throw new \RuntimeException('Unable to write to output file!');}

}}

(c) Carsten Hetzel, 20144

Page 5: Dependency Injection - A practical introduction

Welche Probleme hat diese Klasse?

(c)

Ca

rste

n H

etz

el,

20

14

Page 6: Dependency Injection - A practical introduction

Welche Probleme hat diese Klasse?

Der Dateiname ist nicht änderbar

Es ist nicht klar, wo im Dateisystem die Datei ggf. erzeugt wird

Die Klasse UglyCoupling kann nicht genutzt werden, wenn die Datei nicht geschrieben werden kann

Die Klasse wirft Exceptions bei Fehlern, die mit ihrer eigentlichen Funktion nichts zu tun haben

Wenn weitere Dinge beim Aufruf von "updateOrder()" passieren sollen, müssen sie in "UglyCoupling" hinzugefügt werden

Tests erzeugen Dateien im Dateisystem, auch wenn das auf dem Testsystem vielleicht gar nicht gewünscht ist

(c) Carsten Hetzel, 20146

Page 7: Dependency Injection - A practical introduction

Was ist eigentlich das Problem?

Die Datei wird nicht geschlossen (bzw. erst, wenn der PHP-Prozess endet)

Jede Instanz der Klasse UglyCoupling öffnet die selbe Datei

Wenn der Inhalt des "outputs" oder das Ausgabeformat von Text auf PDF geändert werden soll, muss die Klasse UglyCoupling angepasst werden

...

(c) Carsten Hetzel, 20147

Page 8: Dependency Injection - A practical introduction

Was ist Dependency Injection?

(c)

Ca

rste

n H

etz

el,

20

14

Page 9: Dependency Injection - A practical introduction

Was ist Dependency Injection?

Dependency Injection fordert uns auf Ressourcen anzufordern, statt sie selber bereit zu stellen. Es gibt drei Wege Ressourcen anzufordern:

1. Constructor Injection

2. Setter Injection

3. Interface Injection

(c) Carsten Hetzel, 20149

Page 10: Dependency Injection - A practical introduction

Constructor Injection

Klassen, die Ressourcen für ihre Arbeit benötigen, fordern diese Ressourcen über ihren Konstruktor an.

Damit ist gewährleistet, dass die Klasse von Beginn an über die Ressourcen verfügt, die sie benötigt.

(c) Carsten Hetzel, 201410

Page 11: Dependency Injection - A practical introduction

Constructor Injection

class ConstructorInjection{

/*** @var Service*/private $service;

public function __construct(Service $service){

$this->service = $service;}

}

(c) Carsten Hetzel, 201411

Page 12: Dependency Injection - A practical introduction

Constructor Injection

Ein klarer Nachteil der Constructor Injection ist, dass sehr früh im Application Lifecycle Ressourcen bereitgestellt werden, die evtl. gar nicht zum Einsatz kommen.

Eine Konsequenz aus Constructor Injection ist, Ressourcen so kostengünstig wie möglich zu erstellen, damit keine überflüssige Arbeiten vorgenommen werden.

(c) Carsten Hetzel, 201412

Page 13: Dependency Injection - A practical introduction

Setter Injection

Benötigte Ressourcen werden über Set-Methoden bereitgestellt. Diese Methoden können ggf. deutlich nach der Erstellung des Anfordernden Objekts aufgerufen und dem Klienten bereitgestellt werden.

Dabei besteht aber auch das Risiko einen ungültigen Zustand, ein unvorhersehbares Verhalten oder sogar einen Fehler hervorzurufen.

Ggf. muss also die Verfügbarkeit der Ressourcen geprüft werden!

(c) Carsten Hetzel, 201413

Page 14: Dependency Injection - A practical introduction

Setter Injection

class SetterInjection{

/*** @var Service*/private $service;

/*** @param Service $service*/public function setService(Service $service){

$this->service = $service;}

}

(c) Carsten Hetzel, 201414

Page 15: Dependency Injection - A practical introduction

Interface Injection

Benötigt eine Klasse eine bestimmte Ressource, dann implementiert sie eine Interface, welches das Injizieren dieser Ressource anfordert.

D.h. das Interface fordert die Implementierung z.B. einer "inject"-Methode, die als Parameter die entsprechende Ressource erwartet.

(c) Carsten Hetzel, 201415

Page 16: Dependency Injection - A practical introduction

Interface Injection

class InterfaceInjection implements InjectSercvice{

/*** @var Service*/private $service;

public function injectService(Service $service){

$this->service = $service;}

}

(c) Carsten Hetzel, 201416

Page 17: Dependency Injection - A practical introduction

Was ist eigentlich mit einer "Ressource" gemeint?

(c)

Ca

rste

n H

etz

el,

20

14

Page 18: Dependency Injection - A practical introduction

Was sind „Ressourcen“?

Eine Ressource kann eigentlich alles mögliche sein - von einer einfachen Zahl bis zu einer komplexen Service-Klasse.

Es ist also keines Falls so, dass nur Services über Dependency Injection angefordert werden sollen.

Ein klassisches Beispiel sind Parameter für eine Datenbankverbindung.

(c) Carsten Hetzel, 201418

Page 19: Dependency Injection - A practical introduction

Aufgabe: Überarbeiten sie "UglyCoupling“

(c)

Ca

rste

n H

etz

el,

20

14

Page 20: Dependency Injection - A practical introduction

Aufgabe: „UglyCoupling“

Überarbeiten Sie die Klasse "UglyCoupling" so, dass die benötigten Ressourcen angefordert werden.

Bitte führen Sie diese Aufgabe in Teams durch und diskutieren Sie Ihre Ansätze.

Sie haben 5 Minuten Zeit!

(c) Carsten Hetzel, 201420

Page 21: Dependency Injection - A practical introduction

class UglyCoupling{

private $handle;

public function __construct() {$filename = 'output.txt';$handle = @fopen($filename, 'w');if (!$handle) {

throw new \RuntimeException('Unable to open output file!');}$this->handle = $handle;

}

public function updateOrder($orderId, $newValue) {// ... update the order$output = 'Order ' . $orderId . ' has been changed to ' . $newValue . '!';$bytes = fwrite($this->handle, $output);if ($bytes === false) {

throw new \RuntimeException('Unable to write to output file!');}

}}

(c) Carsten Hetzel, 201421

Page 22: Dependency Injection - A practical introduction

Lösungen?

(c)

Ca

rste

n H

etz

el,

20

14

Page 23: Dependency Injection - A practical introduction

Ist das besser?Bitte diskutieren Sie folgende Lösung!

(c)

Ca

rste

n H

etz

el,

20

14

Page 24: Dependency Injection - A practical introduction

class LessUglyCoupling{

private $fileObject;

public function __construct(\SplFileObject $fileObject){

$this->fileObject = $fileObject;}

public function updateOrder($orderId, $newValue){

// ... update the order$output = 'Order ' . $orderId . ' has been changed to ' . $newValue . '!';$bytes = $this->fileObject->fwrite($output);if ($bytes === null) {

throw new \RuntimeException('Unable to write to output file!');}

}}

(c) Carsten Hetzel, 201424

Page 25: Dependency Injection - A practical introduction

LessUglyCoupling

Naja, zumindest kann man jetzt den Dateinamen ändern, aber es wird immer noch in eine Datei ein Text geschrieben.

Außerdem liefert die Funktion "fwrite()" im Fehlerfall einen anderen Rückgabewert als die Methode SplFileObject::fwrite() (nämlich "false" statt "null")!

Darüber hinaus hat sich aber auch das Verhalten unseres Konstruktors geändert: Er wirft plötzlich keine Exceptionmehr! Für den Fall, dass man UnitTests für diese Klasse geschrieben hat, müssen wir diese spätestens jetzt anpassen.

(c) Carsten Hetzel, 201425

Page 26: Dependency Injection - A practical introduction

Eine Sauberere Lösung mit Konsequenzen!Was halten Sie von folgender Lösung?

(c)

Ca

rste

n H

etz

el,

20

14

Page 27: Dependency Injection - A practical introduction

Listen Sie Vor- und Nachteile auf!

class BetterCoupling{

private $updateOrderHandler;

public function __construct(UpdateOrderHandler $handler){

$this->updateOrderHandler = $handler;}

public function updateOrder($orderId, $newValue){

// ... update the order$this->updateOrderHandler->onOrderUpdate($orderId, $newValue);

}}

(c) Carsten Hetzel, 201427

Page 28: Dependency Injection - A practical introduction

Vorteile

Die Klasse "BetterCoupling" muss nicht mehr entscheiden, was beim Aufruf von "updateOrder" alles passiert. Diese Entscheidung trifft nun die Klasse "UpdateOrderHandler".

Wir müssen uns nicht mehr um die Behandlung von Fehlern kümmern, die uns eigentlich gar nicht interessieren.

Es ist kein fester Text mehr vorhanden.

...

(c) Carsten Hetzel, 201428

Page 29: Dependency Injection - A practical introduction

Nachteile

Wir brauchen eine zusätzliche Klasse "UpdateOrderHandler" und haben damit eine neue Abhängigkeit eingeführt.

Sollte man sich (idealer Weise) entschieden haben, dass "UpdateOrderHandler" ein Interface ist, dann haben wir dem System sogar noch weitere PHP-Dateien hinzugefügt.

Es muss auf jeden Fall ein Handler vorhanden sein, oder wir müssen die Implementierung wieder anpassen, so dass die Methode "onOrderUpdate()" nicht aufgerufen wird, wenn es keinen Handler gibt.

(c) Carsten Hetzel, 201429

Page 30: Dependency Injection - A practical introduction

Die beste Lösung!?Ist es möglich, dass wir ohne Abhängigkeiten auskommen?

(c)

Ca

rste

n H

etz

el,

20

14

Page 31: Dependency Injection - A practical introduction

Die beste Lösung?

class NoCoupling{

public function updateOrder($orderId, $newValue){

// ... update the order}

}

(c) Carsten Hetzel, 201431

Page 32: Dependency Injection - A practical introduction

Und was ist mit unserer Ausgabedatei?

(c)

Ca

rste

n H

etz

el,

20

14

Page 33: Dependency Injection - A practical introduction

Und was ist mit unserer Ausgabedatei?

class CouplingByInheritance extends NoCoupling{

public function updateOrder($orderId, $newValue){

parent::updateOrder($orderId, $newValue);// ... now do whatever you need to do!

}}

(c) Carsten Hetzel, 201433

Page 34: Dependency Injection - A practical introduction

CouplingByInheritance

Diese Klasse kann von uns frei gestaltet werden, ohne dass wir die zugrunde liegende Implementierung des fachlichen Problems ändern müssen.

Die Klasse "NoCoupling" kümmert sich also nur noch um das fachliche Problem und die abgeleitete Klasse kann irgend eine der bisher gezeigten Lösungsvarianten umsetzen.

(c) Carsten Hetzel, 201434

Page 35: Dependency Injection - A practical introduction

Wie werden die ganzen Ressourcen zusammengesetzt?

(c)

Ca

rste

n H

etz

el,

20

14

Page 36: Dependency Injection - A practical introduction

Wie werden die ganzen Ressourcen zusammengesetzt?

Das ist ja alles ganz schön, aber jetzt haben wir ein anders Problem:

Ich habe nichts gewonnen, wenn jetzt an der Stelle, an der ich früher "UglyCoupling" eingesetzt habe, eine ganze Reihe von anderen Klassen zusätzlich erzeugen muss!

Wo früher ...

(c) Carsten Hetzel, 201436

Page 37: Dependency Injection - A practical introduction

Vorher

class MyUglyApplication{

public function doSomething(){

// ...

$oderId = $this->providerOrderId();$newValue = $this->providerNewValue();

$myUglyClass = new UglyCoupling();$myUglyClass->updateOrder($oderId, $newValue);

}}

(c) Carsten Hetzel, 201437

Page 38: Dependency Injection - A practical introduction

Jetzt

class EvenMoreUglyApplication{

public function doSomething(){

// ...$filename = 'output.txt';$fileObject = new \SplFileObject($filename);$updateOrderHandler = new UpdateOrderHandler($fileObject);

$oderId = $this->providerOrderId();$newValue = $this->providerNewValue();

$myUglyClass = new CouplingByInheritance($updateOrderHandler);$myUglyClass->updateOrder($oderId, $newValue);

}}

(c) Carsten Hetzel, 201438

Page 39: Dependency Injection - A practical introduction

Refactoring des Ergebnisses

Im Client-Code (also unserer Anwendung) ist es scheinbar nicht besser sondern schlimmer geworden.

Der Code sieht darüber hinaus unleserlich aus.

Aber durch einfaches Refactoring lassen sich sehr schöne und saubere Methoden erstellen, die die einzelnen Ressourcen erstellen

(c) Carsten Hetzel, 201439

Page 40: Dependency Injection - A practical introduction

Refactoring des Ergebnisses

class MyInjectingApplication{

public function doSomething(){

// ...

$oderId = $this->providerOrderId();$newValue = $this->providerNewValue();

$coupledClass = $this->getCoupledClass();$coupledClass->updateOrder($oderId, $newValue);

}...

(c) Carsten Hetzel, 201440

Page 41: Dependency Injection - A practical introduction

Refactoring des Ergebnisses

...protected function getCoupledClass(){

$updateOrderHandler = $this->getUpdateOrderHandler();$coupledClass = new CouplingByInheritance($updateOrderHandler);return $coupledClass;

}

protected function getUpdateOrderHandler(){

$fileObject = $this->getFileObject();$updateOrderHandler = new UpdateOrderHandler($fileObject);return $updateOrderHandler;

}...

(c) Carsten Hetzel, 201441

Page 42: Dependency Injection - A practical introduction

Refactoring des Ergebnisses

...protected function getFileObject(){

return new \SplFileObject($this->getFilename());}

/*** @return string*/protected function getFilename(){

return 'output.txt';}...

(c) Carsten Hetzel, 201442

Page 43: Dependency Injection - A practical introduction

Refactoring des Ergebnisses

Auf diese Weise bleibt der Code leserlich, die Erstellung jeder einzelnen Ressourcen ist in jeweils einer Methode abgebildet und aus welchen Sub-Ressourcen eine angeforderte Ressource zusammengesetzt ist, kann jederzeit ganz gezielt geändert werden.

Der letzte verbleibende Schritt an dieser Stelle wäre zu entscheiden, welche der Ressourcen immer wieder aufs Neue oder nur einmal erstellt werden sollen.

(c) Carsten Hetzel, 201443

Page 44: Dependency Injection - A practical introduction

Dependency Injection und Anwendungen

(c)

Ca

rste

n H

etz

el,

20

14

Page 45: Dependency Injection - A practical introduction

Dependency Injection und Anwendungen

Während das gezeigte Beispiel den Ansatz verfolgt, dass die Anwendung selbst der Dependency Injection Container (also die Komponente, welche das System "zusammensetzt“) ist, gibt es natürlich eine Reihe von Frameworks, welche diese Aufgabe durch Konfigurationsdateien erledigen.

Interessant dabei ist, dass der jeweilige Dependency Injection Container (DIC) in der Regel die Konfigurationsdatei "auscompiliert", also in eine ausführbare Datei umwandelt, welche tatsächlich sehr ähnlich der oben vorgestellten Lösung ist.

(c) Carsten Hetzel, 201445

Page 46: Dependency Injection - A practical introduction

Dependency Injection und Anwendungen

In jedem Fall ermöglicht einem der oben vorgestellte Ansatz zu einem späteren Zeitpunkt die Anwendung relativ einfach auf einen DIC eines Frameworks umzustellen.

Die beteiligten Klassen fordern ja bereits ihre benötigten Ressourcen an, statt sie selber zu erstellen.

(c) Carsten Hetzel, 201446

Page 47: Dependency Injection - A practical introduction

Aufgabe: Anwendungen "Komponieren"

(c)

Ca

rste

n H

etz

el,

20

14

Page 48: Dependency Injection - A practical introduction

Aufgabe: Anwendungen "Komponieren"

Eine Anwendung zu Verwaltung von Rechnungen soll als einen Anwendungsfall folgendes Verhalten umsetzen:

Wenn der Anwender den Prozess "Rechnung Erstellen" (generateBill) aufruft wird

ein PDF der Rechnung erstellt und

ein Rechnungsbericht mit der Anzahl der Seiten der Rechnung ins Logfile geschrieben

Hinweise:

Erstelle "Kommandos" (Commands, siehe Command-Pattern), die Aufgaben kapseln

Lasse Commands die benötigten Ressourcen anfordern

Abstrahiere Teilinformationen (z.B. die Anzahl der Seiten des PDFs)(c) Carsten Hetzel, 201448

Page 49: Dependency Injection - A practical introduction

Präsentieren Sie Ihre Lösungen

(c)

Ca

rste

n H

etz

el,

20

14

Page 50: Dependency Injection - A practical introduction

(c) Carsten Hetzel, 201450