Häufig gestellte Fragen zu DI (FAQ)
- Ist DI ein anderer Name für IoC?
- Was ist ein Service Locator?
- Wann ist es besser, DI nicht zu verwenden?
- Hat die Verwendung von DI Nachteile?
- Wie schreibt man eine Legacy-Anwendung auf DI um?
- Warum wird Komposition der Vererbung vorgezogen?
- Kann Nette DI Container außerhalb von Nette verwendet werden?
- Warum ist die Konfiguration in NEON-Dateien?
- Verlangsamt das Parsen von NEON-Dateien die Anwendung nicht?
- Wie greife ich von meiner Klasse auf Parameter in der Konfigurationsdatei zu?
- Unterstützt Nette PSR-11: Container interface?
Ist DI ein anderer Name für IoC?
Inversion of Control (IoC) ist ein Prinzip, das sich darauf konzentriert, wie Code ausgeführt wird – ob Ihr Code
fremden Code ausführt oder ob Ihr Code in fremden Code integriert ist, der ihn anschließend aufruft. IoC ist ein weit gefasster
Begriff, der Ereignisse, das sogenannte Hollywood-Prinzip und andere Aspekte umfasst. Teil
dieses Konzepts sind auch Fabriken, über die Regel Nr. 3: Überlasse
es der Fabrik spricht und die eine Inversion für den new
-Operator darstellen.
Dependency Injection (DI) konzentriert sich darauf, wie ein Objekt von einem anderen Objekt, also von seinen Abhängigkeiten, erfährt. Es handelt sich um ein Entwurfsmuster, das die explizite Übergabe von Abhängigkeiten zwischen Objekten erfordert.
Man kann also sagen, dass DI eine spezifische Form von IoC ist. Allerdings sind nicht alle Formen von IoC im Hinblick auf die Code-Reinheit geeignet. Zu den Anti-Patterns gehören beispielsweise Techniken, die mit globalem Zustand arbeiten oder der sogenannte Service Locator.
Was ist ein Service Locator?
Es handelt sich um eine Alternative zur Dependency Injection. Er funktioniert so, dass ein zentraler Speicher erstellt wird, in dem alle verfügbaren Dienste oder Abhängigkeiten registriert sind. Wenn ein Objekt eine Abhängigkeit benötigt, fordert es diese vom Service Locator an.
Im Vergleich zur Dependency Injection geht jedoch die Transparenz verloren: Abhängigkeiten werden den Objekten nicht direkt übergeben und sind daher nicht leicht zu identifizieren, was eine Untersuchung des Codes erfordert, um alle Verknüpfungen aufzudecken und zu verstehen. Das Testen ist ebenfalls komplizierter, da wir Mock-Objekte nicht einfach an die zu testenden Objekte übergeben können, sondern über den Service Locator gehen müssen. Darüber hinaus stört der Service Locator das Code-Design, da einzelne Objekte von seiner Existenz wissen müssen, was sich von der Dependency Injection unterscheidet, bei der Objekte keine Kenntnis vom DI-Container haben.
Wann ist es besser, DI nicht zu verwenden?
Es sind keine Schwierigkeiten im Zusammenhang mit der Verwendung des Dependency Injection-Entwurfsmusters bekannt. Im Gegenteil, das Abrufen von Abhängigkeiten von global verfügbaren Orten führt zu einer ganzen Reihe von Komplikationen, ebenso wie die Verwendung des Service Locators. Daher ist es ratsam, DI immer zu verwenden. Dies ist kein dogmatischer Ansatz, sondern es wurde einfach keine bessere Alternative gefunden.
Dennoch gibt es bestimmte Situationen, in denen wir Objekte nicht übergeben und sie aus dem globalen Raum beziehen. Zum Beispiel beim Debuggen von Code, wenn Sie an einem bestimmten Punkt im Programm den Wert einer Variablen ausgeben, die Dauer eines bestimmten Programmteils messen oder eine Nachricht protokollieren müssen. In solchen Fällen, in denen es sich um temporäre Aufgaben handelt, die später aus dem Code entfernt werden, ist es legitim, einen global verfügbaren Dumper, eine Stoppuhr oder einen Logger zu verwenden. Diese Werkzeuge gehören nämlich nicht zum Code-Design.
Hat die Verwendung von DI Nachteile?
Bringt die Verwendung von Dependency Injection Nachteile mit sich, wie z. B. erhöhten Schreibaufwand oder Leistungseinbußen? Was verlieren wir, wenn wir anfangen, Code gemäß DI zu schreiben?
DI hat keinen Einfluss auf die Leistung oder den Speicherbedarf der Anwendung. Die Leistung des DI-Containers kann eine gewisse Rolle spielen, aber im Falle des Nette DI wird der Container zu reinem PHP kompiliert, sodass sein Overhead während der Laufzeit der Anwendung praktisch null ist.
Beim Schreiben von Code ist es oft notwendig, Konstruktoren zu erstellen, die Abhängigkeiten akzeptieren. Früher konnte dies mühsam sein, aber dank moderner IDEs und constructor property promotion ist dies jetzt eine Frage von Sekunden. Fabriken können mit Nette DI und einem Plugin für PhpStorm einfach per Mausklick generiert werden. Andererseits entfällt die Notwendigkeit, Singletons und statische Zugriffspunkte zu schreiben.
Man kann feststellen, dass eine korrekt entworfene Anwendung, die DI verwendet, im Vergleich zu einer Anwendung, die Singletons verwendet, weder kürzer noch länger ist. Teile des Codes, die mit Abhängigkeiten arbeiten, werden lediglich aus den einzelnen Klassen extrahiert und an neue Orte verschoben, d. h. in den DI-Container und die Fabriken.
Wie schreibt man eine Legacy-Anwendung auf DI um?
Der Übergang von einer Legacy-Anwendung zur Dependency Injection kann ein anspruchsvoller Prozess sein, insbesondere bei großen und komplexen Anwendungen. Es ist wichtig, diesen Prozess systematisch anzugehen.
- Beim Übergang zur Dependency Injection ist es wichtig, dass alle Teammitglieder die verwendeten Prinzipien und Verfahren verstehen.
- Führen Sie zunächst eine Analyse der bestehenden Anwendung durch und identifizieren Sie die Schlüsselkomponenten und ihre Abhängigkeiten. Erstellen Sie einen Plan, welche Teile refaktorisiert werden und in welcher Reihenfolge.
- Implementieren Sie einen DI-Container oder verwenden Sie besser eine vorhandene Bibliothek, z. B. Nette DI.
- Refaktorisieren Sie nach und nach einzelne Teile der Anwendung, um Dependency Injection zu verwenden. Dies kann Anpassungen von Konstruktoren oder Methoden beinhalten, sodass sie Abhängigkeiten als Parameter akzeptieren.
- Passen Sie die Stellen im Code an, an denen Objekte mit Abhängigkeiten erstellt werden, sodass stattdessen die Abhängigkeiten vom Container injiziert werden. Dies kann die Verwendung von Fabriken beinhalten.
Denken Sie daran, dass der Übergang zur Dependency Injection eine Investition in die Codequalität und die langfristige Wartbarkeit der Anwendung ist. Auch wenn es anspruchsvoll sein kann, diese Änderungen durchzuführen, sollte das Ergebnis ein saubererer, modularerer und leicht testbarer Code sein, der für zukünftige Erweiterungen und Wartung bereit ist.
Warum wird Komposition der Vererbung vorgezogen?
Es ist ratsamer, Komposition anstelle von Vererbung zu verwenden, da sie zur Wiederverwendung von Code dient, ohne sich um die Folgen von Änderungen kümmern zu müssen. Sie bietet also eine lockerere Kopplung, bei der wir keine Bedenken haben müssen, dass die Änderung eines Codes die Notwendigkeit zur Änderung eines anderen abhängigen Codes verursacht. Ein typisches Beispiel ist die Situation, die als constructor hell bezeichnet wird.
Kann Nette DI Container außerhalb von Nette verwendet werden?
Auf jeden Fall. Der Nette DI Container ist Teil von Nette, aber er ist als eigenständige Bibliothek konzipiert, die unabhängig von den anderen Teilen des Frameworks verwendet werden kann. Installieren Sie ihn einfach mit Composer, erstellen Sie eine Konfigurationsdatei mit der Definition Ihrer Dienste und erstellen Sie dann mit wenigen Zeilen PHP-Code den DI-Container. Und schon können Sie die Vorteile der Dependency Injection in Ihren Projekten nutzen.
Wie die konkrete Verwendung einschließlich des Codes aussieht, beschreibt das Kapitel Nette DI Container.
Warum ist die Konfiguration in NEON-Dateien?
NEON ist eine einfache und leicht lesbare Konfigurationssprache, die im Rahmen von Nette für die Konfiguration von Anwendungen, Diensten und deren Abhängigkeiten entwickelt wurde. Im Vergleich zu JSON oder YAML bietet sie für diesen Zweck wesentlich intuitivere und flexiblere Möglichkeiten. In NEON lassen sich Verknüpfungen natürlich beschreiben, die in Symfony & YAML entweder gar nicht oder nur durch eine komplizierte Umschreibung möglich wären.
Verlangsamt das Parsen von NEON-Dateien die Anwendung nicht?
Obwohl NEON-Dateien sehr schnell geparst werden, spielt dieser Aspekt überhaupt keine Rolle. Der Grund dafür ist, dass das Parsen der Dateien nur einmal beim ersten Start der Anwendung erfolgt. Danach wird der Code des DI-Containers generiert, auf der Festplatte gespeichert und bei jeder weiteren Anfrage ausgeführt, ohne dass weiteres Parsen erforderlich ist.
So funktioniert es in der Produktionsumgebung. Während der Entwicklung werden NEON-Dateien jedes Mal geparst, wenn sich ihr Inhalt ändert, damit der Entwickler immer über einen aktuellen DI-Container verfügt. Das eigentliche Parsen ist, wie gesagt, eine Frage von Augenblicken.
Wie greife ich von meiner Klasse auf Parameter in der Konfigurationsdatei zu?
Denken wir an Regel Nr. 1: Lass es dir übergeben. Wenn eine Klasse Informationen aus der Konfigurationsdatei benötigt, müssen wir nicht darüber nachdenken, wie wir an diese Informationen gelangen, sondern wir fordern sie einfach an – zum Beispiel über den Konstruktor der Klasse. Und die Übergabe erfolgt in der Konfigurationsdatei.
In diesem Beispiel ist %myParameter%
ein Platzhalter für den Wert des Parameters myParameter
, der an
den Konstruktor der Klasse MyClass
übergeben wird:
# config.neon
parameters:
myParameter: Some value
services:
- MyClass(%myParameter%)
Um mehrere Parameter zu übergeben oder Autowiring zu nutzen, ist es ratsam, die Parameter in ein Objekt zu verpacken.
Unterstützt Nette PSR-11: Container interface?
Nette DI Container unterstützt PSR-11 nicht direkt. Wenn Sie jedoch Interoperabilität zwischen dem Nette DI Container und Bibliotheken oder Frameworks benötigen, die das PSR-11 Container Interface erwarten, können Sie einen einfachen Adapter erstellen, der als Brücke zwischen dem Nette DI Container und PSR-11 dient.