Singleton Problem
-
hustbaer schrieb:
Globale Variable und Singleton sind was alle relevanten Punkte betrifft identisch.
So lange du mit Fällen zu tun hast, wo dies Zutrifft, kann es sich von vornherein schon nicht um potentielle Anwendungsfälle für das Singleton Pattern handeln, da keine Forderung nach einer Konstruktionsbeschränkung des Typs besteht und die notwendigen Voraussetzungen für den Einsatz eines Singleton damit niemals erfüllt sein können...
Oder anders ausgedrückt: So lange es keinen Unterschied machen würde, ob du eine globale Variable oder ein Singleton verwendest, ist Singleton unangebracht...
-
Bei einem Singleton weiß jeder Entwickler, dass es nur eines geben soll und er erstellt kein zweites bzw kann es garnicht.
Richtig, aber man muss aufpassen, dass man es nicht übertreibt. Wenn es sehr viele Singletons gibt, ist die Wiederverwendbarkeit enorm eingeschränkt, Unit-Tests sind sehr umständlich zu schreiben und Abhängigkeiten werden verschleiert, weil anhand der Schnittstelle einer Klasse nicht mehr deutlich wird, wie diese sind. DI mag etwas mehr Aufwand sein, aber es löst diese Probleme.
Bei einer Klasse die eigentlich ein Singleton sein sollte, aber nicht als solche implementiert ist, sondern nur als per DI an alle Klasse weitergegeben wird weiß nicht jeder Entwickler, dass er irgendwo die übergebene Klasse herbekommen und verwenden muss und sich nicht seine eigene erstellen soll.
Beispiel? Ich finde nicht, dass selbst erstellen eine Alternative ist. Wenn einen Teil der Software erweitere und dort ein Objekt einer Klasse nutzen möchte, dann überlege ich mir, wo die Klasseninstanz logisch hingehört. Sind Daten davon auch auf höherer Ebene notwendig? Dann ist wohl logisch, dass das Objekt auf der höheren Ebene ansässig ist.
hustbaer:
Du meinst, Singletons sollte man dann ggü. globalen Variablen einsetzen, wenn man Exklusivität durchsetzen möchte? Wie wenn ein Singleton z.B. die Schnittstelle zu einem Drucker darstellt (unter Annahme, dass der einfach jedes ihm gesendete Zeichen sofort druckt)?Bei so klaren Auffassungen über die (Nicht-)Anwendbarkeit dieses Patterns sollte es doch einfach sein Mal ein paar klare Regeln zu formulieren. Darüber könnte man sich Mal strukturiert unterhalten.
-
xdrectfvzgbh schrieb:
Jetzt wirds mal interessanter. Die Singleton Diskussion ist sowieso langweilig und dreht sich im Kreis.
Wenn du die Diskussion um Unit-Tests vertiefen willst, mach einen neuen Thread auf. Über die Unternehmensstruktur meines Arbeitgebers muss ich mich natürlich bedeckt halten; hier nur so viel: Selbstverständlich schreibe ich Mock-Klassen, wo das zum Testen Sinn macht (sprich: wo Klassen polymorphisch verwendet werden). 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.
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.
-
seldon: Falsch. Dependency Injection != Using an IoC container
-
Ich lass mal einen Link auf http://martinfowler.com/articles/injection.html da; da kommt der Begriff nämlich her. Du solltest dir insbesondere den Abschnitt "Inversion of Control" mal durchlesen, wo der Autor erklärt, wie der Zusammenhang zwischen dependency injection und inversion of control ist. Spoiler: DI ist eine Art von IoC.
-
dot schrieb:
hustbaer schrieb:
Globale Variable und Singleton sind was alle relevanten Punkte betrifft identisch.
So lange du mit Fällen zu tun hast, wo dies Zutrifft, kann es sich von vornherein schon nicht um potentielle Anwendungsfälle für das Singleton Pattern handeln, da keine Forderung nach einer Konstruktionsbeschränkung des Typs besteht und die notwendigen Voraussetzungen für den Einsatz eines Singleton damit niemals erfüllt sein können...
Und wenn eine Konstruktionsbeschränkung des Typs besteht, dann gilt eben 1 Singleton = N globale Variablen + M freie Funktionen.
(Wobei die "globalen" Variablen gerne file-scope-static sein dürfen, also ich meine mit "global" nicht dass die öffentlich zugänglich sein müssen.)Gerade wenn "eine Konstruktionsbeschränkung des Typs besteht" ist der Typ eigentlich kein Typ, sondern einfach ein Haufen freier Funktionen die sich ein bisschen State teilen, und die man in eine Klasse packt damits "objektiger" aussieht.
-
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.