dependency injection - a practical introduction
TRANSCRIPT
![Page 1: Dependency Injection - A practical introduction](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/1.jpg)
Dependency InjectionEine Einführung mit grundlegenden Beispielen(c
) C
ars
ten
He
tze
l, 2
014
![Page 2: Dependency Injection - A practical introduction](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/2.jpg)
Was ist eigentlich das Problem?
(c)
Ca
rste
n H
etz
el,
20
14
![Page 3: Dependency Injection - A practical introduction](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/3.jpg)
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](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/4.jpg)
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](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/5.jpg)
Welche Probleme hat diese Klasse?
(c)
Ca
rste
n H
etz
el,
20
14
![Page 6: Dependency Injection - A practical introduction](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/6.jpg)
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](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/7.jpg)
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](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/8.jpg)
Was ist Dependency Injection?
(c)
Ca
rste
n H
etz
el,
20
14
![Page 9: Dependency Injection - A practical introduction](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/9.jpg)
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](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/10.jpg)
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](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/11.jpg)
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](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/12.jpg)
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](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/13.jpg)
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](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/14.jpg)
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](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/15.jpg)
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](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/16.jpg)
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](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/17.jpg)
Was ist eigentlich mit einer "Ressource" gemeint?
(c)
Ca
rste
n H
etz
el,
20
14
![Page 18: Dependency Injection - A practical introduction](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/18.jpg)
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](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/19.jpg)
Aufgabe: Überarbeiten sie "UglyCoupling“
(c)
Ca
rste
n H
etz
el,
20
14
![Page 20: Dependency Injection - A practical introduction](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/20.jpg)
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](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/21.jpg)
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](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/22.jpg)
Lösungen?
(c)
Ca
rste
n H
etz
el,
20
14
![Page 23: Dependency Injection - A practical introduction](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/23.jpg)
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](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/24.jpg)
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](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/25.jpg)
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](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/26.jpg)
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](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/27.jpg)
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](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/28.jpg)
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](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/29.jpg)
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](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/30.jpg)
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](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/31.jpg)
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](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/32.jpg)
Und was ist mit unserer Ausgabedatei?
(c)
Ca
rste
n H
etz
el,
20
14
![Page 33: Dependency Injection - A practical introduction](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/33.jpg)
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](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/34.jpg)
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](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/35.jpg)
Wie werden die ganzen Ressourcen zusammengesetzt?
(c)
Ca
rste
n H
etz
el,
20
14
![Page 36: Dependency Injection - A practical introduction](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/36.jpg)
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](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/37.jpg)
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](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/38.jpg)
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](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/39.jpg)
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](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/40.jpg)
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](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/41.jpg)
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](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/42.jpg)
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](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/43.jpg)
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](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/44.jpg)
Dependency Injection und Anwendungen
(c)
Ca
rste
n H
etz
el,
20
14
![Page 45: Dependency Injection - A practical introduction](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/45.jpg)
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](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/46.jpg)
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](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/47.jpg)
Aufgabe: Anwendungen "Komponieren"
(c)
Ca
rste
n H
etz
el,
20
14
![Page 48: Dependency Injection - A practical introduction](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/48.jpg)
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](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/49.jpg)
Präsentieren Sie Ihre Lösungen
(c)
Ca
rste
n H
etz
el,
20
14
![Page 50: Dependency Injection - A practical introduction](https://reader033.vdocuments.us/reader033/viewer/2022052316/55a05ddd1a28ab372e8b45ba/html5/thumbnails/50.jpg)
(c) Carsten Hetzel, 201450