Dependency Injection

Berial

Neues Mitglied
Hallo,

ich habe vor mehr oder minder kurzer Zeit mit Java angefangen und habe schon das eine oder andere kleinere Programm entwickelt. Dabei bin ich aber noch weit von professionellen Techniken wie z.B. TDD entfernt.

Zumindest (JUnit-)Tests möchte ich endlich für meine Anwendungen haben. Dabei bin ich über typische Probleme gestolpert, welche offenbar mithilfe von Dependency Injection umgangen werden können. Ich habe mir bereits die Dokumentation von z.B. Guice (welches ich vor allem wegen GIN benutzen möchte, um letztlich auch GWT-Anwendungen zu testen) angeschaut. Die Beispiele die ich gesehen habe, erscheinen mir logisch und ich denke, dass ich sie begriffen habe.

Jedoch sind die bisherigen Beispiele immer nur in der Form »Klasse B verwendet Klasse C. Anstelle Klasse C selbst zu instanziieren, bekommt Klasse B eine Referenz über z.B. den Konstruktor.«.
Auf Guice bezogen holt sich Klasse A (main(), Entry Point) einen Injector, erzeugt damit Klasse B und C und übergibt B die Referenz auf C.

Aber wie skaliert dieses Vorgehen auf beliebig viele Klassen? Was ist wenn ein Objekt der Klasse C ein Objekt der Klasse D benötigt. Dann würde doch Klasse C die Abhängigkeit von Klasse B erwarten, welche letztlich auf Klasse A bei der Erzeugung von C zurückführt.

Das heißt doch dann letztlich, dass der gesamte Abhängigkeitsbaum nahezu direkt nach Programmstart vorhanden sein muss, auch wenn B z.B. C dieses mal nicht benutzt?! Den Injektor rumschleppen, damit B (wenn es wirklich notwendig ist) C erzeugen kann, ist ja auch nicht der Sinn der Sache.

Irgendwie verstehe ich DI "im Großen" noch nicht so wirklich. Könnte mir da jemand auf die Sprünge helfen?
 

Noctarius

Top Contributor
Spring verwaltet Abhängigkeiten extern vom eigentlichen Programmcode in XML Dateien, den so genannten AppContext Descriptoren. Hierbei kannst du auch nach der Kompilation noch Abhängigkeiten austauschen. Bei Guice ist das etwas schwierig, da die Abhängigkeiten direkt programmiert werden (aber auch hier gibt es Möglichkeiten die Abhängigkeiten extern als XML zu hinterlegen). Guice ist eher für solche Fälle wie einfaches Unittesting geschrieben, wo ich zur Programmierzeit weiß welche Abhängigkeiten ich zu welchem Stand habe (eben Development oder Production oder ...).
Abgesehen davon löst DI noch einige andere Probleme wie z.B. sauberes Singleton Pattern.
 

Landei

Top Contributor
Aber wie skaliert dieses Vorgehen auf beliebig viele Klassen?

Das hat bei mir ein wenig länger gedauert, bis ich es begriffen habe: Konstruktor-Injektion ist "ansteckend": Wenn ich mir in Klasse A eine Klasse B über den Guice Injector holen lasse, und Klasse B im Konstruktor Klasse C braucht und Klasse C im Konstruktor Klasse D braucht, injizierst du mit einem Aufruf gleich über drei Ebenen. Man muss wirklich intensiv überlegen, wie man den Code enstprechend strukturieren kann, dass das so funktioniert. Ansonsten muss man den Injector rumreichen, neu erzeugen u.s.w., was der Idee von DI widerspricht und total unpraktisch ist.
 
B

bygones

Gast
Aber wie skaliert dieses Vorgehen auf beliebig viele Klassen? Was ist wenn ein Objekt der Klasse C ein Objekt der Klasse D benötigt. Dann würde doch Klasse C die Abhängigkeit von Klasse B erwarten, welche letztlich auf Klasse A bei der Erzeugung von C zurückführt.
mal schaun ob ich das aufdröseln kann..

Wenn Objekt C ein Objekt D braucht und Objekt C ein Objekt B, dann muss natürlich erstmal D in C injeziert werden, so dass dann C in B injiziert werden kann.

Das heißt doch dann letztlich, dass der gesamte Abhängigkeitsbaum nahezu direkt nach Programmstart vorhanden sein muss, auch wenn B z.B. C dieses mal nicht benutzt?! Den Injektor rumschleppen, damit B (wenn es wirklich notwendig ist) C erzeugen kann, ist ja auch nicht der Sinn der Sache.
bei Guice "schleppst" du nix rum. Du sagst ja nur "falls C gebraucht wird, dann muss ein D injeziert werden". Es ist ja nicht so, dass Guice zum Start mal alles komplett erstellt, sondern eben nur dann wenn ein entsprechendes Objekt auch abgerufen wird.
Du musst aber natürlich zum Start sagen, welche Implementierung von C genommen werden soll.
Wenn dies "dynamisch" behandelt werden muss, so gibt es in Guice zB Provider die das regeln können.

Guice ist eher für solche Fälle wie einfaches Unittesting geschrieben
das stimmt so nicht... Guice kann in jeder Java Applikation zum Einsatz kommen (google nutzt es, Maven mittlerweilen auch) - es ist nicht auf testing beschränkt !
 

Noctarius

Top Contributor
Dazu haben sowohl Spring als auch Guice sehr gute Algorithmen um mögliche zirkulare Abhängigkeiten aufzulösen, z.B. durch precaching von Instanzen ohne diese wirklich schon fertig zu injecten.


edit:
das stimmt so nicht... Guice kann in jeder Java Applikation zum Einsatz kommen (google nutzt es, Maven mittlerweilen auch) - es ist nicht auf testing beschränkt !

So war das auch nicht gemeint, aber im Gegensatz zu Spring geschieht das Verdrahten der Abhängigkeiten im Code also zum Zeitpunkt des Programmierens, damit sind eher Fälle wie Development, Unittest, Production über Guice lösbar als nachträgliches Verändern per Konfiguration (geht auch aber mit Guice selbst nicht).
 
Zuletzt bearbeitet:

KSG9|sebastian

Top Contributor
Das ist ja genau der Vorteil von GUICE gegenüber Spring:

Spring löst alles über XML-Dateien (ja, ich weiß - manches geht auch über Annotation) und Stringidentifier. Damit verliert man jegliche Prüfungen zum Compilezeit, es gibt keine Typsicherheit, kein Refactoringsupport (außer mit der Spring IDE).

Guice bringt genau diese Dinge mit. Durch Annotations hat man statisches Typing. Es werden keine Identifier verwendet sondern man bindet direkt. Für dynamische Bindings kann man z.B. Provider verwenden.
Lazy-Bindings können sehr einfach über einen Proxymechanismus gebaut werden. Anstatt die Implementierung direkt zu injecten kann man einen Proxy verwenden. Dieser Proxy wiederum erzeugt beim ersten Zugriff auf ein Objekt die Instanz.

Code:
class ProxyHandler implements InvocationHandler{
   .. invoke(.....){
       if(target == null){
            // lazy-Erzeugung des eigentlichen Objekts
            getGuiceInjector().getInstance(MyConcretImpl.class);
       }
   }
}

Ich persönlich favorisiere Guice da es viel "smarter" ist und die oben genannten Vorteile bietet. Letztendlich, so denke ich, ist es aber eine "Gefühlsentscheidung".
 

Noctarius

Top Contributor
Richtig das ist Vor- und Nachteil.

Wenn man viele Kunden hat und z.B. Workflows integriert ist die nachträgliche XML Version besser ;-) Es kommt immer auf den Blickwinkel an. Theoretisch bin ich aber auch Typen-Fan und mag Guice daher. Aber Sinn haben beide Varianten.
 

KSG9|sebastian

Top Contributor
Ich habe den Eindruck, dass man vieles, das man in Spring mit XML machen kann, in Guice durch Property-Dateien oder den SPI-ServiceLoader abfackeln kann, siehe z.B.:

http://www.java-forum.org/665367-post4.html
http://www.java-forum.org/666603-post7.html
FrequentlyAskedQuestions - google-guice - Project Hosting on Google Code (Frage "How do I inject configuration parameters?")

Der Sinn von Guice ist es genau das nicht zu tun - denn damit verliert man statisches Typing.

Wenn man erweiterbare Module hat kann man das über GuiceModule regeln. So kann jeder Nutzer/jeder der das Produkt anpasst auch sein eigenes Guicemodul einbinden und darüber wiederum DI verwenden.
 

Landei

Top Contributor
Das Argument zieht nur bei den Properties-Dateien, denn der ServiceLoader liefert ganz typsicher Instanzen eines gegebenen Interfaces. Und bestimmte Low-Level-Sachen wie Verbindungsstrings sind nun einmal praktischer in einer Properties-Datei oder so, als irgendwo hartkodiert in einer Klasse, denn ich will ja nicht jedesmal das ganze Gedöns neu compilieren, nur weil sich irgendwo eine IP oder so ändert. Und da das in der Regel Sachen sind, die sich selten ändern, und laufen, wenn sie erst einmal laufen, kann man durchaus in Kauf nehmen, dass die Anwendung die Hufe hochhebt, wenn da Quatsch drinsteht. Aber auch im Fall von Properties-Dateien entkoppelt Guice die Settings von der Applikation.

Und genau darum geht es bei Guice: Ums Entkoppeln. Statische Typsicherheit ist nur das Sahnehäubchen.
 

KSG9|sebastian

Top Contributor
Binden von Konstanten u.s.w. aus Properties-Dateien macht natürlich Sinn. Den ServiceLoader für "dynamisches" binden auch.

Ich hab's so verstanden dass eine "Kombination" verwendet werden soll: In einer Properties-Datei stehen voll qualifizierte Klassennamen welche dann für Bindings angezogen werden -> und das finde ich nicht gut.

Beides für sich ist natürlich sehr schon und auch sinnvoll einzusetzen!
 

Ähnliche Java Themen


Oben