Singleton Problem
-
dot schrieb:
Singleton und globale Variablen sind zwei völlig voneinander unabhängige Dinge...
Nein, in C++ zumindest kann man keine Singletons ohne globale/statische Variablen implementieren. Hatten wir übrigens schon

Oder anders ausgedrückt: Das Singleton-Pattern ist nicht die logische Konsequenz daraus, eine Klasse nur einmal instanziieren zu wollen. Zusätzlich muss auch das Bedürfnis nach einem gemeinsamen Zugriffspunkt bestehen.
-
dot schrieb:
Nathan schrieb:
Man nehme ein globals std::ostream Objekt?
Also sowas wie std::cout!? Wieso sollte sowas ein Singleton sein?
Ist eine globale Variable ein Singleton?
Bleiben wir beim Logger, std::cout, std::clog, std::cerr + eigener Logger sind alles Instanzen von std::ostream. Nach einer Instanz sieht das nicht gerade aus.looogggeer schrieb:
Nathan schrieb:
Man nehme ein globals std::ostream Objekt?
Also wieder globale Variablen, statt Singletons. Weil Singletons jetzt böse sind und globale Variablen es früher waren, aber das schon alle vergessen haben.
Wahl zwischen Pest und Cholera. :p
Nein, std::cout ist eine begründete globale Variable. Da gibt es auch keine Alternative, weil std::ostream kein Singleton ist.
-
Ach, hier stand Käse.
-
@Morle
Jo.
Dein Beispiel fällt für mich in die selbe Kategorie wie auch non-nesting Init/Release Funktionen.Bei sowas finde ich nen "Singleton" als Workaround OK. Wobei ich hier auch normale freie Funktionen als Wrapper vorziehen würde.
Irgendwo muss es aber irgend ein "Singleton" geben, weil es darf ja nur eine Mutex geben. (Wobei dieses "Singleton" nicht unbedingt das "Singleton-Pattern" verwenden muss, es tut's auch eine globale Variable +call_onceo.ä.)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?
pS: Bei deinem Beispiel einen Zeiger auf irgend eine Wrapper-Klasse rumzureichen wäre mMn. komplett verkehrt. Würde nur einen weiteren unnötigen Bruchpunkt schaffen.
-
Nexus schrieb:
Oder anders ausgedrückt: Das Singleton-Pattern ist nicht die logische Konsequenz daraus, eine Klasse nur einmal instanziieren zu wollen.
Natürlich nicht, das hab ich auch nie behauptet, ganz im Gegenteil. Zweck des Singleton Pattern ist es, auszudrücken, dass es sich um eine prinzipielle Eigenschaft eines Typs handelt, dass es immer nur eine Instanz dieses Typs geben kann (vgl. mit dem gleichnamigen Konzept aus der Mathematik). Der globale Zugriffspunkt (der streng genommen übrigens eigentlich nicht global ist, da er nicht globalen Scope hat; aber ich denk, wir wissen beide, was damit gemeint ist) ist imo lediglich als Konsequenz der üblichen Implementierung eines Singleton zu sehen und auf keinen Fall ist das Bedürfnis nach einem globalen Zugriffspunkt hinreichende Bedingung für die Anwendung des Singleton Pattern. Das Wesen des Singleton steckt in der Konstruktionsbeschränkung; die ist, was ein Singleton ausmacht, ohne die gibt's per Definition kein Singleton. Und Konstruktionsbeschränkung und globaler Zugriffspunkt sind zwei völlig orthogonale Konzepte...
Nathan schrieb:
dot schrieb:
Nathan schrieb:
Man nehme ein globals std::ostream Objekt?
Also sowas wie std::cout!? Wieso sollte sowas ein Singleton sein?
Ist eine globale Variable ein Singleton?
Wie oben geschrieben sind "globale Variable" und "Singleton" unabhängige Konzepte. Eine globale Variable ist ein Objekt, als Singleton bezeichnet man dagegen einen Typ mit bestimmten Eigenschaften. Natürlich könnte der Typ einer globalen Variable rein prinzipiell ein Singleton sein, auch wenn es in C++ vermtulich keinen Weg gibt, sowas direkt zu bauen. Aber weder "ist" eine globale Variable notwendigerweise ein Singleton, noch ist die eine Instanz eines Singleton notwendigerweise eine globale Variable...
-
dot schrieb:
xdrectfvzgbh schrieb:
dot schrieb:
Die richtige Frage ist eher, was es genau für einen Grund gibt, aus dem sichergestellt werden muss, dass die fragliche Klasse um jeden Preis nur einmal instanziert werden kann. Oder anders ausgedrückt: Sag mir, wieso es in der Geschichte der Menschheit nie jemals ein zweites Objekt dieser Klasse wird geben dürfen.
Du hast Singleton nicht verstanden. Das hat nichts mit der Geschichte der Menschheit zu tun, sondern nur mit der Programminstanz. Darum findest du auch nie einen Anwendungsfall.
Ich bin ganz Ohr, vielleicht kannst du mir und den anderen Unwissenden hier ja die Augen öffnen!?
Jedes Ding, dass es für eine spezielle Anwendung nur einmal geben soll und von dem jeder Entwickler wissen soll, dass er hier genau dieses verwenden soll ist ein Singleton. Wenn bestimmte Resourcen nur einmal geladen werden sollen, mache ich ein Singleton, dass diese cached (das bedeutet nicht, jeder Cahce ist ein Singleton (ja ich weiß, du willst alles falsch verstehen)). Du wirts diesen Cache zwar viel lieber an 10000 Klassen weiter geben und dort als Member halten, aber das machen dann sowieos wieder andere Entwickler falsch und erstellen sich ihren eigenen Cache, was dem Anwendungskonzept widerspricht. Aber du entwickelst sowieso nur allein, stimmst?
-
Wie machst du das dann, wenn du Unit-Tests schreibst oder den selben Code mal in mehreren Anwendungen verwenden willst?
Und wenn du mit Entwicklern arbeitest, die es nicht hinkriegen, ein übergebenes Objekt zu verwenden, kannst du dich dann wirklich darauf verlassen, dass die mit deinem Wald von Singletons zurechtkommen? Mit Verlaub, dieses Konzept klingt wie die Rückkehr zur schlechten alten Zeit vor der Erfindung der Objektorientierung.
-
dot schrieb:
Nathan schrieb:
dot schrieb:
Nathan schrieb:
Man nehme ein globals std::ostream Objekt?
Also sowas wie std::cout!? Wieso sollte sowas ein Singleton sein?
Ist eine globale Variable ein Singleton?
Wie oben geschrieben sind "globale Variable" und "Singleton" unabhängige Konzepte. Eine globale Variable ist ein Objekt, als Singleton bezeichnet man dagegen einen Typ mit bestimmten Eigenschaften. Natürlich könnte der Typ einer globalen Variable rein prinzipiell ein Singleton sein, auch wenn es in C++ vermtulich keinen Weg gibt, sowas direkt zu bauen. Aber weder "ist" eine globale Variable notwendigerweise ein Singleton, noch ist die eine Instanz eines Singleton notwendigerweise eine globale Variable...
Unsinniger Formalismus.
Globale Variable und Singleton sind was alle relevanten Punkte betrifft identisch.
Ausschlaggebend ist einzig und alleine das Highlander-Prinzip. Ob das auf einen ganzen Typ angewendet wird, oder bloss eine Instanz eines Typs die zu einem bestimmten Zweck verwendet wird ist in Wirklichkeit egal.
-
Oh man hier geht ja die Post ab.
Hab das meiste jetzt nur überflogen, ohne großartige Erkenntnisse.Aber ist auch nicht so schlimm. Ich hab erstmal eine zufrieden stellende Lösung gefunden.
-
Und wenn du mit Entwicklern arbeitest, die es nicht hinkriegen, ein übergebenes Objekt zu verwenden, kannst du dich dann wirklich darauf verlassen, dass die mit deinem Wald von Singletons zurechtkommen?
Bei einem Singleton weiß jeder Entwickler, dass es nur eines geben soll und er erstellt kein zweites bzw kann es garnicht. 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.
seldon schrieb:
Wie machst du das dann, wenn du Unit-Tests schreibst oder den selben Code mal in mehreren Anwendungen verwenden willst?
Jetzt wirds mal interessanter. Die Singleton Diskussion ist sowieso langweilig und dreht sich im Kreis. Erzähl mal wie du das machst. Schreibst du für unittests für alle Klasse die nicht zum Modul gehören Mocks? Wieviel deines Codes verwendest du in mehreren Anwendungen? Wie verschieden sind die Anwendungen? Was ist das für Code den du wiederverwendest, sowas wie allgemeine Listen, und Bildverarbeitungsalgorithmen usw. oder was anwendungsspezifisches? Wieviele Leute arbeiten mit dem Code?
-
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.