Singleton Problem
-
seldon schrieb:
Im Übrigen scheint dir nicht ganz klar zu sein, was dependency injection bedeutet. Es hat nichts mit dem Herumreichen von Referenzen auf bestehende Objekte zu tun (und schon gar nicht mit Singletons, ganz im Gegenteil!), sondern dreht sich darum, die konkrete Klasse von Datenmembern einer Klasse nicht in dieser Klasse festzulegen, sondern einem Injektor zu überlassen, der die Abhängigkeit dann zur Laufzeit injiziert.
Soweit noch: ja.
Das ist in C++ eine sehr krampfige Angelegenheit und eher selten eine gute Idee; es ist nicht völlig unmöglich, aber im Grunde braucht man dafür Zeigersemantik und eine VM mit Reflection.
Schmarrn.
Für DI braucht man keine Reflection. Zeiger bzw. Referenzen ja, klar. Aber wie du auf Reflection kommt ist mir schleierhaft.Steht sogar in dem von dir verlinkten Artikel ein super schönes Beispiel dafür wie einfach DI sein kann:
PicoContainer uses a constructor to decide how to inject a finder implementation into the lister class. For this to work, the movie lister class needs to declare a constructor that includes everything it needs injected.
class MovieLister... public MovieLister(MovieFinder finder) { this.finder = finder; }Das ist das Grundprinzip von DI.
Geht in C++ genau so, alles was man dafür braucht sind Zeiger bzw. Referenzen (C++ Referenzen tun's in dem Fall genau so) und virtuelle Funktionen (bzw. was vergleichbares, also Funktionszeiger würden genau so reichen).
Reflection würde man bloss brauchen, wenn man Dinge machen will für die man üblicherweise eben DI-Frameworks hernimmt. Wie z.B. Objektgraphen aus Daten die aus nem Config-File kommen zusammenzustoppeln. Wobei natürlich auch das ohne Reflection möglich wäre, nur da würde es dann wirklich anfangen ohne Reflection reichlich lästig zu werden.
-
Ein Objekt, das eine Referenz auf irgendetwas hält allein macht noch keine dependency injection, sonst fiele da auch std::ref drunter. Nur ein paar Absätze hinter dem, was du zitierst, wird auch beschrieben, welche Rolle pico container in diesem Beispiel spielen -- die sind nämlich der Dreh- und Angelpunkt der ganzen Angelegenheit.
Es steht auch ganz ausdrücklich ein bisschen weiter oben:
The basic idea of the Dependency Injection is to have a separate object, an assembler, that populates a field in the lister class with an appropriate implementation for the finder interface, resulting in a dependency diagram along the lines of Figure 2
Und direkt darüber, unter den Überschriften "A Naive Example" und "Inversion of Control" steht auch erklärt, was das ganze soll und ist, nämlich eine Methode, sich Objektgraphen aus einer Beschreibung $irgendwoanders zusammenzustoppeln. Wir reden hier über eine Methode, Plugin-Systeme zu implementieren.
-
Ich muss sagen, ich verstehe DI durchaus auch so, wie hustbaer es beschreibt, und ich denke so wird es auch üblich dargestellt. Sagen wir Klasse A will eine Datei schreiben. Jetzt kann es in Klasse A selbst stehen, wie man eine Datei schreibt, oder Klasse A verwendet einen globalen Dateischreiber, oder man übergibt A den Dateischreiber im ctor(das ist der injection Teil) den er zum schreiben braucht (das wäre die dependency). Welche Art von Dateischreiber das ist kann man sich dann letztendlich für jede Klasse, die eine Dateischreiben aussuchen, was z.B. ein Vorteil gegenüber einem globalem Dateischreiber wäre. Letztlich dreht es sich wohl nur um ein semantisches Argument.
Aber mal noch zu singletons:
Niemand, selbst die, die singletons etwas verteidigen, will davon einen Wald haben. Ich würde sagen mehr als eine einstellige Anzahl ist zuviel, so als Richtlinie. Für mich ist das schlicht und einfach eine Methode, um Konsistenz sicherzustellen - das z.B. alle Teile des Programms auf den selben Daten arbeitet. Ein Beispiel:
Ich habe ein Modul, das lua-states bereitstellt. Diese sollen aber alle dieselben (zusätzlichen) C(++) funktionen haben. Welche das sind, wird bei Programmstart festgelegt. Angenommen ich realisiere das mit einer Klasse, die sich merkt wie es festgelegt wurde - wie Stelle ich den sicher, das nicht jemand ausversehen diese Klasse nochmal erstellt, ohne die festgelegten Funktionen? Das würde dann zu inkonsquenz und eventuell Fehlern führen (die sich in diesem Beispiel darin äußern, das manche lua skripte manche Funktionen einfach nicht kennen). Ich sehe dann eigentlich keinen Grund, das nicht zu verbieten.
Dann gebe ich nämlich eine Garantie: Wenn du meine Klasse verwendest um einen lua-state zu bekommen, hat der state auch alle festgelegten Funktionen definiert. Ich wüsste mal nicht, wie ich diese Garantie in code ohne Singleton ähnlich klar geben könnte.
Um klarzustellen: Wenn sich jemand selbst einen lua-state zusammenbasteln will (oder ihn komplett "leer" lässt), dann darf er das auch ruhig machen.
Die Aufgabe vom Singleton ist nicht "lua-state erstellen" sondern "lua-state mit Konsistenzgarantie erstellen".
-
@seldon
Dann lies mal bei Wikipedia nach: http://en.wikipedia.org/wiki/Dependency_injectionWas du meinst ist automatic dependency injection. Das automatic ist aber nicht notwendig damit etwas DI ist.
-
seldon schrieb:
Im Übrigen scheint dir nicht ganz klar zu sein, was dependency injection bedeutet. Es hat nichts mit dem Herumreichen von Referenzen auf bestehende Objekte zu tun (und schon gar nicht mit Singletons, ganz im Gegenteil!), sondern dreht sich darum, die konkrete Klasse von Datenmembern einer Klasse nicht in dieser Klasse festzulegen, sondern einem Injektor zu überlassen, der die Abhängigkeit dann zur Laufzeit injiziert. Das ist in C++ eine sehr krampfige Angelegenheit und eher selten eine gute Idee; es ist nicht völlig unmöglich, aber im Grunde braucht man dafür Zeigersemantik und eine VM mit Reflection.
Du kennst dots Idee nicht wie er Singletons mit DI ersetzen will. Den Rest haben dir schon andere erklärt.
-
seldon schrieb:
Ebenso selbstverständlich schreibe ich all meinen Code so, dass er prinzipiell ohne größere Probleme an anderer Stelle erneut verwendet werden könnte -- ganz unabhängig davon, ob der Fall später tatsächlich eintritt, gebietet das schon die Wartbarkeit, denn deine Anwendung muss hinterher erweiterbar sein. Wenn mal zusätzlich Daten aus einer zweiten Datenquelle verarbeitet werden sollen, stehst du ziemlich dumm da, wenn nicht nur deine Datenbankverbindungsklasse ein Singleton ist, sondern sich auch all dein Code darauf verlässt, dass das der Fall ist. Und erfahrungsgemäß tritt der Fall, dass Code wiederverwendet wird, in dem Maße häufiger auf, in dem dein Code wiederverwendbar ist. Das dürfte in die Kategorie der Dinge fallen, die ziemlich offensichtlich sind, nachdem sie einmal erwähnt wurden.
Jetzt hast du leider nicht gesagt, wieviel deines Codes tatsächlich wiederverwendet wird. Da sieht man dann erst, ob er wirklich wiederverwendbar ist, wenn die verendenden Anwendungen auch wirklich unterschiedlich sind.
-
Auch der Wikipedia-Artikel, den ich übrigens für weniger autoritativ halte als den Artikel des Menschen, der den Begriff geprägt hat, erwähnt als Kernkomponente von dependency injection den Assembler (nennt ihn injector). Nochmal:
The basic idea of the Dependency Injection is to have a separate object, an assembler, that populates a field in the lister class with an appropriate implementation for the finder interface, resulting in a dependency diagram along the lines of Figure 2
Das heißt, du baust dein Objekt nicht mehr selber, sondern wendest dich an den Assembler, der von $irgendwo weiß, wie man Objekte des Typs, den du brauchst, zusammenbaut. Oder gehst mit einem halbfertigen Objekt an den Assembler, um es fertigbauen zu lassen, das sind dann Implementationsdetails. Aber ohne die Grundidee der ganzen Angelegenheit ist das keine dependency injection, und wer auch immer das Codebeispiel in der Wikipedia unter "manually injected dependency" geschrieben hat, hat das Konzept umgedeutet. Das kann man tun, aber dann bleibt uns nichts anderes übrig, als festzuhalten, dass wir über völlig unterschiedliche Dinge (die an einer Stelle ähnlich aussehen) unter dem gleichen Namen reden. Der IoC-Container, der in Martin Fowlers Definition ganz zentral ist, fällt auf diese Weise völlig weg.
Jetzt muss der Assembler seine Objektdefinitionen nicht aus einer Konfigurationsdatei lesen oder per Reflection aus Annotationen holen, um dependency injection zu bauen, aber richtig sinnvoll wird das ganze erst durch solche oder ähnliche Mechanismen.
@kleiner Troll: Ich bin mir nicht sicher, richtig verstanden zu haben, was du da vorhast. Das mag vielleicht damit zusammenhängen, dass ich lua nur flüchtig kenne. Du hast Factory für Lua-States, die nur einmal zentral existiert und willst deren Konsistenz sicherstellen, verstehe ich das richtig? Da hängt vieles von Implementationsdetails ab, die ich nicht kenne, denke ich. Mein erster Gedanke wäre, dass für die Konsistenz neuer Objekte einer Klasse der Konstruktor verantwortlich ist, und da das wenig mit Singletons zu tun hat, vermute ich, dass er völlig an der Sache vorbeigeht.
@xdrectfoo: Natürlich wird viel in Anwendungen wiederverwendet, die die gleiche oder ähnliche Domänen abdecken, das liegt in der Natur der Sache. Und auch daran, dass ein Unternehmen in der Regel ein Gebiet hat, auf dem es unterwegs ist. Wenn deine Vorstellung von Wederverwendbarkeit einschließt, dass eine Trinomialbaumfaltung zur Implementation eines HTTP-Servers verwendbar sein sollte, unterscheidet sie sich stark von meiner.
-
hustbaer schrieb:
Welche Dinge als Workaround OK sind oder nicht finde ich aber nicht wirklich ein sehr spannendes Thema. Was sein muss muss eben sein.
Hast du auch ein Beispiel wo ein Singleton vorkommt das nicht Teil eines Workarounds ist?
Nein. Das wollte ich dem OP mit meinem Beispiel aber auch gar nicht zeigen. Wenn möglich versuche ich Singletons (und globale Variablen) zu vermeiden. Ich lebe aber nicht in einer idealen Programmierwelt wo ich immer die schönste und zeitaufwendigste Lösung umsetzten kann, sondern ich muss regelmäßig Kompromisse eingehen und benötige deswegen auch ab und zu Singletons. Und ein solches Beispiel wollte ich geben. Ich wollte *nicht* sagen, jeder muss eine DLL Zugriffe mit Singletons lösen.
-
kleiner Troll schrieb:
Aber mal noch zu singletons:
Niemand, selbst die, die singletons etwas verteidigen, will davon einen Wald haben. Ich würde sagen mehr als eine einstellige Anzahl ist zuviel, so als Richtlinie. Für mich ist das schlicht und einfach eine Methode, um Konsistenz sicherzustellen - das z.B. alle Teile des Programms auf den selben Daten arbeitet. Ein Beispiel:
Ich habe ein Modul, das lua-states bereitstellt. Diese sollen aber alle dieselben (zusätzlichen) C(++) funktionen haben. Welche das sind, wird bei Programmstart festgelegt. Angenommen ich realisiere das mit einer Klasse, die sich merkt wie es festgelegt wurde - wie Stelle ich den sicher, das nicht jemand ausversehen diese Klasse nochmal erstellt, ohne die festgelegten Funktionen? Das würde dann zu inkonsquenz und eventuell Fehlern führen (die sich in diesem Beispiel darin äußern, das manche lua skripte manche Funktionen einfach nicht kennen). Ich sehe dann eigentlich keinen Grund, das nicht zu verbieten.So.
Und jetzt baust du mit dem Lua Gedöns ein wirklich tolles Toolkit für irgendwas und packst das in dieDasToolkit.DLL.
DasToolkit.DLList extrem praktisch, und wird daher in mehreren Projekten verwendet.In
CooleSache.DLLaus Projekt A werden damit ganz coole Sachen gemacht. Und inOhYeahMan.DLLauf Projekt B werden damit noch viel coolere Sachen gemacht.Und weil die beiden DLLs so extrem cool sind sollen sie in Projekt C beide zum Einsatz kommen. Die Skripte die
OhYeahMan.DLLverarbeitet sind aber nicht vertrauenswürdig, die können vom User eingegeben werden oder per Email daherkommen oder was auch immer. Macht aber nix,OhYeahMan.DLLstellt eh keine pösen Funktionen zur Verfügung, die Lua-Skripte laufen alle schön in einer Sandbox und malen nur lustigen Animationen auf den Bildschirm.Dummerweise registriert
CooleSache.DLLaber so praktische Funktionen wieFormalAllMyHarddrives. Und weils so praktisch und sicher ist verwendetDasToolkit.DLLein Singleton, und gibt an alle die selben Lua-States aus. Und dann schick' ich dir eine Email die deinen PC flachwalzt.Boom, you're dead.
----
Und jetzt noch eine konkrete Antwort auf eine rhetorische Frage:
kleiner Troll schrieb:
wie Stelle ich den sicher, das nicht jemand ausversehen diese Klasse nochmal erstellt, ohne die festgelegten Funktionen?
Antwort: Gar nicht.
-
seldon schrieb:
Auch der Wikipedia-Artikel, den ich übrigens für weniger autoritativ halte als den Artikel des Menschen, der den Begriff geprägt hat, erwähnt als Kernkomponente von dependency injection den Assembler (nennt ihn injector). Nochmal:
The basic idea of the Dependency Injection is to have a separate object, an assembler, that populates a field in the lister class with an appropriate implementation for the finder interface, resulting in a dependency diagram along the lines of Figure 2
Das heißt, du baust dein Objekt nicht mehr selber, sondern wendest dich an den Assembler, der von $irgendwo weiß, wie man Objekte des Typs, den du brauchst, zusammenbaut.
Ja, und $irgendwo kann sein dass da hardcoded drinnen steht was zu tun ist. Ist das so schwer zu verstehen? Der Injector ist einfach die Komponente/Stelle im Programm die zusammenbaut. Wo der Bauplan herkommt ist dabei vollkommen egal. Dort könnte genau so "the caller" stehen.
und wer auch immer das Codebeispiel in der Wikipedia unter "manually injected dependency" geschrieben hat, hat das Konzept umgedeutet. Das kann man tun, aber dann bleibt uns nichts anderes übrig, als festzuhalten, dass wir über völlig unterschiedliche Dinge (die an einer Stelle ähnlich aussehen) unter dem gleichen Namen reden.
Nein, es sind überhaupt nicht völlig unterschiedliche Dinge. Der Knackpunkt bei DI ist nicht der automatische Assembler sondern die IoC.
Hast du einen Vorschlag wie man "manual DI" sonst nennen sollte?
Jetzt muss der Assembler seine Objektdefinitionen nicht aus einer Konfigurationsdatei lesen oder per Reflection aus Annotationen holen, um dependency injection zu bauen, aber richtig sinnvoll wird das ganze erst durch solche oder ähnliche Mechanismen.
Falsch, DI ist auch ohne das "richtig Sinnvoll". Nur dass es ohne Config-Files nicht jo "objektiv", "javaig" und "enterprisig" ist.
Wo man das aber nicht braucht macht man DI ohne Config-Files, und ist damit immer noch viel viel besser unterwegs als ohne DI.ps:
Ich hab den Artikel von Fowler auch gelesen. Ich kann darin keinen Hinweis darauf finden dass er den Begriff DI im Zusammenhang mit irgendwelchen automatischen "Assemblern" versteht. Überall wo der Bergiff vorkommt geht es lediglich darum dass das Erzeugen von Dependencies nicht der selbe Programmteil macht wie der der diese Dependencies dann braucht.Den Rest interpretierst du wohl dazu, weil du es einfach so verstehen willst.
-
Häng dich nicht an den Konfigurationsdateien auf, um die geht es nun wirklich nicht. Ob der Assembler den Kram hardgecodet hat, aus einer Konfigurationsdatei/datenbank/whatever liest oder zufallsgeneriert, ob er FooFactory heißt oder eine Funktion ist, ist Implementationssache, aber völlig wegnehmen kannst du ihn nicht. Es langt nicht, in einem Konstruktor Referenzen oder Zeiger entgegenzunehmen und sich diese zu merken. Und ich weiß wirklich nicht, wie viel klarer als "The basic idea of the Dependency Injection is to have a separate object, an assembler" der Autor das noch machen könnte.
Es geht nicht darum, Abhängigkeiten zwischen Objekten festzulegen, sondern ein fertiges Objekt eines Typs zu bauen, ohne dass dieser die konkreten Klassen seiner Datenmember kennen muss. Und das muss halt irgendwer machen.
Vielleicht nochmal um klarzustellen, was ich mit "im Grunde braucht man dafür Zeigersemantik und eine VM mit Reflection" meine, weil dich das mehr zu bewegen scheint als der eigentliche Hauptpunkt:
Ich will nicht ausdrücken, dass dependency injection mit hardgecodeten Abhängigkeiten im Assembler unmöglich ist, sondern lediglich, dass das dann (insbesondere für die Anwendungsfälle, für die sich die Pattern ursprünglich herausgebildet hat) nur noch eingeschränkt nützlich ist. Üblicherweise willst du die Abhängigkeiten zur Laufzeit auflösen, wenn du dependency injection benutzt. Es sei denn, du machst das wirklich ausschließlich zu Testzwecken.
Und in Bezug auf C++ ist es einfach eine Menge Overhead, weil du mit cacheunfreundlichen, verlinkten Datenstrukturen endest, wo du eigentlich gern alles direkt hintereinander hättest und die Allokationen eine Menge Zeit fressen, die eigentlich nicht vergeben werden müsste. Wenn deine Sprache sowieso Zeigersemantik für komplexe Typen hat und dein kompaktierender Garbage-Collector die Heap-Allokation trivial macht, fällt die Problematik gegenüber in der Klasse selbst festgecodeter Abhängigkeiten weniger bis überhaupt nicht ins Gewicht, und das Konzept wird attraktiver.
-
seldon schrieb:
Häng dich nicht an den Konfigurationsdateien auf, um die geht es nun wirklich nicht. Ob der Assembler den Kram hardgecodet hat, aus einer Konfigurationsdatei/datenbank/whatever liest oder zufallsgeneriert, ob er FooFactory heißt oder eine Funktion ist, ist Implementationssache, aber völlig wegnehmen kannst du ihn nicht. Es langt nicht, in einem Konstruktor Referenzen oder Zeiger entgegenzunehmen und sich diese zu merken. Und ich weiß wirklich nicht, wie viel klarer als "The basic idea of the Dependency Injection is to have a separate object, an assembler" der Autor das noch machen könnte.
Es geht nicht darum, Abhängigkeiten zwischen Objekten festzulegen, sondern ein fertiges Objekt eines Typs zu bauen, ohne dass dieser die konkreten Klassen seiner Datenmember kennen muss. Und das muss halt irgendwer machen.
Genau, es muss irgendwer machen.
Es "langt" aber sehr wohl dass man eine Klasse hat die im Konstruktor einen Zeiger auf etwas mitgegeben bekommt was sie braucht. Also vorausgesetzt die Klasse konstruiert sich nicht selbst.
Man hat dann nämlich genau zwei Möglichkeiten:
A: Man verwendet die Klasse nicht. Dass dieser Fall nicht Gegenstand einer sinnvollen Diskussion sein kann sollte hoffentlich klar sein.
B: Man verwendet die Klasse irgendwo. Dann MUSS es einen anderen Programmteil geben der die Klasse "zusammenbaut", also den von dir erwähnten Assembler.
"Assembler" ist ja nur eine Rolle die irgendein Programmteil übernimmt, wo und wie ist nicht wirklich wesentlich.
Vielleicht nochmal um klarzustellen, was ich mit "im Grunde braucht man dafür Zeigersemantik und eine VM mit Reflection" meine, weil dich das mehr zu bewegen scheint als der eigentliche Hauptpunkt:
Ich will nicht ausdrücken, dass dependency injection mit hardgecodeten Abhängigkeiten im Assembler unmöglich ist, sondern lediglich, dass das dann (insbesondere für die Anwendungsfälle, für die sich die Pattern ursprünglich herausgebildet hat) nur noch eingeschränkt nützlich ist. Üblicherweise willst du die Abhängigkeiten zur Laufzeit auflösen, wenn du dependency injection benutzt. Es sei denn, du machst das wirklich ausschließlich zu Testzwecken.
Du hast noch nicht vile Libraries entwickelt, oder? Es geht dabei doch nicht primär ums Testen, es geht um flexibles Softwaredesign, Reusability etc.
D.h. die Klasse die man flexibel/wiederverwendbar implementieren möchte sollte DI unterstützen statt starr auf genau ein Szenario hinprogrammiert zu sein. Ob dann in einem speziellen Programm das diese Klasse verwendet Unterscheidungen zur Laufzeit gibt ist egal.
Wichtig ist dass das Programm bestimmen kann welche Implementierung einer Dependency genau verwendet wird, und nicht darauf festgenagelt ist was die Klasse selbst für sinnvoll hält.Und in Bezug auf C++ ist es einfach eine Menge Overhead, weil du mit cacheunfreundlichen, verlinkten Datenstrukturen endest, wo du eigentlich gern alles direkt hintereinander hättest und die Allokationen eine Menge Zeit fressen, die eigentlich nicht vergeben werden müsste. Wenn deine Sprache sowieso Zeigersemantik für komplexe Typen hat und dein kompaktierender Garbage-Collector die Heap-Allokation trivial macht, fällt die Problematik gegenüber in der Klasse selbst festgecodeter Abhängigkeiten weniger bis überhaupt nicht ins Gewicht, und das Konzept wird attraktiver.
Sicher wird DI "attaktiver" wenn man ne VM mit Reflection und was nicht noch alles hat.
Das bedeutet aber weder dass dies der bei DI wesentliche Punkt wäre, noch dass DI in Sprachen ohne VM/Reflection uninteressant wäre.
-
hustbaer schrieb:
Es "langt" aber sehr wohl dass man eine Klasse hat die im Konstruktor einen Zeiger auf etwas mitgegeben bekommt was sie braucht. Also vorausgesetzt die Klasse konstruiert sich nicht selbst.
Nein, das langt nicht. Wesentlicher Bestandteil ist, dass die injizierten Objekte nachher Datenmember des Objektes sind, in das sie injiziert wurden, also exklusiv diesem (jetzt C++-Lingo) gehören. Es geht um eine besteht-aus-, nicht um eine benutzt-Beziehung.
Wenn du irgendwo ein std::ostream-Objekt hinlegst und dieses 20 Objekten als Logger bekannt machst, spricht man nicht von dependency injection.
In Bezug auf
hustbaer schrieb:
B: Man verwendet die Klasse irgendwo. Dann MUSS es einen anderen Programmteil geben der die Klasse "zusammenbaut", also den von dir erwähnten Assembler.
"Assembler" ist ja nur eine Rolle die irgendein Programmteil übernimmt, wo und wie ist nicht wirklich wesentlich.
hast du allerdings recht. Es sieht ziemlich merkwürdig aus, das inline zu machen, und vor diesem Hintergrund (und weil ich gedanklich noch in dieser Diskussion war) habe ich wohl das Codestück auf der Wikipedia (manual injection) falsch gelesen.
Und letztlich zu
hustbaer schrieb:
seldon schrieb:
Vielleicht nochmal um klarzustellen, was ich mit "im Grunde braucht man dafür Zeigersemantik und eine VM mit Reflection" meine, weil dich das mehr zu bewegen scheint als der eigentliche Hauptpunkt:
Ich will nicht ausdrücken, dass dependency injection mit hardgecodeten Abhängigkeiten im Assembler unmöglich ist, sondern lediglich, dass das dann (insbesondere für die Anwendungsfälle, für die sich die Pattern ursprünglich herausgebildet hat) nur noch eingeschränkt nützlich ist. Üblicherweise willst du die Abhängigkeiten zur Laufzeit auflösen, wenn du dependency injection benutzt. Es sei denn, du machst das wirklich ausschließlich zu Testzwecken.
Du hast noch nicht vile Libraries entwickelt, oder? Es geht dabei doch nicht primär ums Testen, es geht um flexibles Softwaredesign, Reusability etc.
Nochmal durchlesen. Ich habe nicht gesagt, was du gehört hast.
-
seldon schrieb:
hustbaer schrieb:
Es "langt" aber sehr wohl dass man eine Klasse hat die im Konstruktor einen Zeiger auf etwas mitgegeben bekommt was sie braucht. Also vorausgesetzt die Klasse konstruiert sich nicht selbst.
Nein, das langt nicht. Wesentlicher Bestandteil ist, dass die injizierten Objekte nachher Datenmember des Objektes sind, in das sie injiziert wurden, also exklusiv diesem (jetzt C++-Lingo) gehören. Es geht um eine besteht-aus-, nicht um eine benutzt-Beziehung.
Wenn du irgendwo ein std::ostream-Objekt hinlegst und dieses 20 Objekten als Logger bekannt machst, spricht man nicht von dependency injection.
Hm.
Weiss nicht.
Ich sehe da keinen relevanten Unterschied.
Aus dem Namen "Dependency Injection" kann man es auch nicht ableiten.Die besseren DI Container bieten ja auch ne Möglichkeit an Objekte zu "sharen".
Wieso sollte es einen Unterschied machen ob das Ding nur dem einen Objekt gehört oder mehreren?
seldon schrieb:
Nochmal durchlesen. Ich habe nicht gesagt, was du gehört hast.
OK. Nochmal.
seldon schrieb:
(...) dass das dann (insbesondere für die Anwendungsfälle, für die sich die Pattern ursprünglich herausgebildet hat) nur noch eingeschränkt nützlich ist. Üblicherweise willst du die Abhängigkeiten zur Laufzeit auflösen, wenn du dependency injection benutzt. Es sei denn, du machst das wirklich ausschließlich zu Testzwecken.
Wieso sollte man das üblicherweise wollen?
Ich will das üblicherweise nicht, weil ich keinen Anwendungsfall dafür habe.
Ich will nur, dass Komponenten nichts bestimmen was sie nicht bestimmen müssen bzw. wofür sie einfach nicht zuständig sind.Du kannst nicht einfach von dir auf die Allgemeinheit schliessen.
-
das mag jetzt vielleicht ein bisschen provokant klingen, aber ich meine es nicht so, meine Frage ist ernst gemeint.
Ist Dependency Injection nicht eine furchtbar komplizierte Beschreibung einer einfachen Sache welche man auf ein pseudo akademisches Niveau erhoben und verkompliziert hat (damit manche Buchautoren einen Grund haben ihre Werke an den Mann zu bringen und darüber referieren können)?
Kann man das nicht einfach so zusammenfassen:
Ich will Code testen welcher in der Echtumgebung auf Daten zugreifen muss die zur Testzeit nicht zur Verfügung stehen und daher logischerweise im Test über einen anderen Mechanismus erzeugt und bereitgestellt werden müssen damit ich den Test auch ausführen kann.
Wie man das am besten macht hängt jeweils von den gegebenen Umständen ab, ein Rezept für alles kann es nicht geben.ist DI etwas anderes oder geht DI über das hinaus?
sory darür das ich das in den Singleton Thread schreib, ich denke aber der hat sein Topic eh schon etwas gewechselt.
-
ist DI etwas anderes oder geht DI über das hinaus?
Genau das wird doch seit einigen Seiten diskutiert. Ich schlage vor Du liest Dir das alles erstmal durch...
-
kurze_frage schrieb:
Ist Dependency Injection nicht eine furchtbar komplizierte Beschreibung einer einfachen Sache welche man auf ein pseudo akademisches Niveau erhoben und verkompliziert hat (damit manche Buchautoren einen Grund haben ihre Werke an den Mann zu bringen und darüber referieren können)?
Und eine furchtbar wichtig klingende Beschreibung einer vollkommen unwichtigen Sache.
-
Naja, das kann man so oder sorum sehen. Wir kommen hier aus einer ziemlich enterprisigen Denkrichtung -- dass der ganze Kram mit Assemblern etc. von Fowler als "lightweight" beschrieben wird, macht das recht deutlich.
Im Grunde geht es bei dependency injection mehr um ein Verwendungs- als ein Implementationsmuster. Du willst die konkreten Klassen der Datenmember deines Objekts nicht in dessen Klasse selbst bestimmen müssen, also lagerst du es aus, und anstatt das in einen service locator zu tun (der auch nur durch seine Verwendung prinzipiell etwas anderes als eine factory ist), aus dem sich der Konstruktor jener Klasse die konkreten Objekte holen kann, setzt du bei dependency injection einen Assembler/Injektor hin, der die konkreten Datenmember des Objektes vorher selbst baut und dem Konstruktor des Objekts übergibt (oder sie auf andere Weise in dieses Objekt schiebt).
In der Praxis wird das durch Dinge kompliziert, die in dieser Kerndefinition nicht vorkommen. Wenn du ein Dependency-Injection-Framework auspackst, bringt das gerne Dinge mit, die den ganzen Verwaltungsaufwand so gut wie möglich vor dem Client-Code verstecken. Ich nehme mal Google Guice als Beispiel: da wird mit Annotations rangeschrieben, welche Konstruktoren für Injection gedacht sind und welche Methoden eines Abhängigkeitsauflösungsmoduls (TODO: besseren Namen finden) Abhängigkeitsobjekte erstellen, und der Injektor, der vorher mit einem solchen Modul initialisiert wurde, sucht sich per Reflection raus, welche Methoden des Moduls er benutzen kann, schreibt diese völlig um, und sucht sich raus, welche Konstruktoren der Klasse, von der er ein Objekt bauen soll, er verwenden kann. Im Client-Code steht dann zur Erstellung der eigentlichen Objekte nur noch etwa
Foo bar = injector.getInstance(Foo.class);Details hier: https://code.google.com/p/google-guice/wiki/Motivation
Um den Bogen zu Singletons zurückzuschlagen: Wo die Diskussion ursprünglich mal herkam, ist, dass das Rumreichen eines Objekts, auf das andere Objekte dann Referenzen zur Verwendung halten sollen (dies wurde als Alternative zu Singletons erwähnt und ist auch in der Tat besser) keine dependency injection ist. Ich halte es auch nicht für sinnvoll, den Begriff so weit aufweichen zu wollen, dass allein das halten eines Zeigers-auf-Basisklasse dependency injection darstellt, weil dann auch Strategy, Composite sowie ein halbes Dutzend anderer Design Patterns unter den Begriff fiele und std::unique_ptr ein Beispiel für DI wäre.
-
vielen Dank für die Erläuterung seldon