Datenbankzugriffsschicht ohne mit Singletons um sich zu schmeißen?



  • Mach doch mal das Beispiel von oben.



  • Was genau ist daran jetzt bitte noch unklar!?



  • Machst du sowas?

    class KlasseB
    {
    public:
      KlasseB(Log fileLogger, Log windowLogger)...
    }
    

    oder

    class KlasseB
    {
    public:
      KlasseB(Log fileAndWindowLogger)...
    }
    


  • Ich denke auch, dass man Singletons nicht für DB-Verbindungen nutzen sollte. Ich hab hier eine historisch gewachsene Software bei der die Instanz der DB-Verbindung immer von der obersten Ebene als Parameter an die nächst tiefere reingereicht wird. Es gab immer nur eine Instanz der DB-Verbindung und die änderte sich eigentlich auch nicht. Hab damals überlegt, ob man die Verbindung auch in einem Singleton unterbringen und in den entsprechenden Funktionen abfragen könnte. Bloß gut, dass wir das nicht gemacht haben.

    Zwei Jahre später kam nämlich heraus, dass unter bestimmten Umständen (und an ganz bestimmten Stellen) eine andere DB-verbindung (mit anderem Zeichensatz) zur selben DB benötigt wird. Die können wir ganz bequem an den entsprechenden Stellen reinreichen und alles Funktioniert wie bisher. Bei der singleton-Variante hätten wir erst den Code und die möglichen Seiteneffekte analysieren müssen.

    Mir persönlich gefällt das Reinreichen der Instanz in jede Funktion aber auch nicht.

    ähm schrieb:

    Machst du sowas?

    class KlasseB
    {
    public:
      KlasseB(Log fileLogger, Log windowLogger)...
    }
    

    oder
    ...

    Das würde mich auch mal interessieren. Kann ja nicht schaden mal eine Alternative zu sehen.



  • dot schrieb:

    BierzeltOmi schrieb:

    Thread-Sicherheit?

    Dependency Injection

    Quatsch. Nur weil du die Klasse übergibst ändert das nichts. Du musst immer noch locken vor du von verschiedenen Threads ins gleiche Log-File schreibst.



  • yxcvbnm schrieb:

    dot schrieb:

    BierzeltOmi schrieb:

    Thread-Sicherheit?

    Dependency Injection

    Quatsch. Nur weil du die Klasse übergibst ändert das nichts. Du musst immer noch locken vor du von verschiedenen Threads ins gleiche Log-File schreibst.

    Das musst du aber in jedem Fall, daher ging ich davon aus dass BierzeltOmi das nicht gemeint haben kann denn das wäre ja sinnlos. Bei der DI Variante ist zumidest immer klar definiert wer wann welches Log verwendet...



  • ähm schrieb:

    Machst du sowas?

    Nein ich mach sowas:

    class KlasseB
    {
    public:
      KlasseB(Log& log, ...) ...
    }
    

    Was genau für eine Art von Log ich übergeb ist für KlasseB irrelevant.



  • Ok und das nennt sich schon Dependency Injection? Ich hatte mir etwas kniffligeres vorgestellt, aber gut.. 🙂



  • @dot: Findest du constructor-injection gut weil du es hier dauernd praktizierst?

    Und vielleicht sollten wir für C++ nicht solcherlei Java-Beispiele bringen, da das dort ohnehin nicht state-of-the-art ist wie volkard uns bereits mitgeteilt hat.

    MfG SideWinder



  • SideWinder schrieb:

    @dot: Findest du constructor-injection gut weil du es hier dauernd praktizierst?

    Und vielleicht sollten wir für C++ nicht solcherlei Java-Beispiele bringen, da das dort ohnehin nicht state-of-the-art ist wie volkard uns bereits mitgeteilt hat.

    MfG SideWinder

    Okay, wie sieht denn dann das typische state-of-the-art c++ Beispiel aus?



  • Dependency Injection im Constructor bringt zwar ein paar Vorteile gegenüber einem Singleton, aber braucht auch mehr Speicher und Performance, wenn man z.B. 1000000 Objekte erstellt und denen allen einen Logger zuweist.



  • SideWinder schrieb:

    @dot: Findest du constructor-injection gut weil du es hier dauernd praktizierst?

    Was gäbe es denn für Alternativen in C++?

    bewertung schrieb:

    Dependency Injection im Constructor bringt zwar ein paar Vorteile gegenüber einem Singleton, aber braucht auch mehr Speicher und Performance, wenn man z.B. 1000000 Objekte erstellt und denen allen einen Logger zuweist.

    Der Zugriff auf einen Singleton über die getInstance() Methode ist auch kein Leichtgewicht. Und wenn du 100000 Objekte hast die alle auf einen anderen Logger loggen können müssen hast du sowieso ganz andere Probleme...



  • dot schrieb:

    SideWinder schrieb:

    @dot: Findest du constructor-injection gut weil du es hier dauernd praktizierst?

    Was gäbe es denn für Alternativen in C++?

    * Setter
    * Thread-Locals



  • hustbaer schrieb:

    * Setter

    Damit ändert sich doch am Prinzip nix!?

    hustbaer schrieb:

    * Thread-Locals

    Das hätte doch eine vollkommen andere Semantik. Das hilft mir doch nur wenn ich die Dependency eben an einen bestimmten Thread binden will und nicht an ein bestimmtes Objekt!? Ich würde das jetzt nicht als Alternative sehen sondern als was völlig Orthogonales.

    Aber die Idee das Logging nicht auf einer per-Komponenten- sondern einer per-Thread-Basis zu machen ist sehr interessant und unter Umständen wirklich sehr elegant 👍 , werd ich mir auf jeden Fall merken 😉



  • dot schrieb:

    hustbaer schrieb:

    * Thread-Locals

    Das hätte doch eine vollkommen andere Semantik. Das hilft mir doch nur wenn ich die Dependency eben an einen bestimmten Thread binden will und nicht an ein bestimmtes Objekt!? Ich würde das jetzt nicht als Alternative sehen sondern als was völlig Orthogonales.

    Naja... 🙂

    Sagen wir mal so...

    Thread-Locals sind ein möglicher Weg, kontextbezogene Lookups zu machen.
    Und kontextbezogene Lookups kann man wiederum verwenden um Inversion of Control zu machen.
    Und Inversion of Control ist schon eng verwandt mit Dependency Injection.

    Nochmal dazu:

    Das hilft mir doch nur wenn ich die Dependency eben an einen bestimmten Thread binden will und nicht an ein bestimmtes Objekt!?

    Es reicht ja oft (meistens?), dass man Dependencies an einen "Task" ("Request", "Job") binden kann.

    Wenn das Programm so strukturiert ist, dass es, während der Lebenszeit eines Tasks, eine 1:1 Beziehung Task:Thread gibt, dann reicht es, z.B. einen Service Locator an einen Thread zu binden.

    Wenn es so eine 1:1 Beziehung nicht gibt wird es etwas komplizierter. Lässt sich aber auch noch machen, man muss dann halt an allen Stellen wo ein Thread anfängt/aufhört für einen Task tätig zu werden die Thread-Local Variable entsprechend anpassen. (Kann man z.B. ohne grossen Aufwand machen, indem man alle Callbacks/Thread-Funktionen in einen Hilf-Funktor einwickelt.)

    Alternativ kann man natürlich den Service Locator bzw. den Task überall als Parameter rumreichen.



  • dot schrieb:

    bewertung schrieb:

    Dependency Injection im Constructor bringt zwar ein paar Vorteile gegenüber einem Singleton, aber braucht auch mehr Speicher und Performance, wenn man z.B. 1000000 Objekte erstellt und denen allen einen Logger zuweist.

    Der Zugriff auf einen Singleton über die getInstance() Methode ist auch kein Leichtgewicht.

    Eigentlich schon, wenn man eine createInstance Methode einführt. Dann fallen auch ein paar andere Probleme wie Initialisierungsreihenfolge weg.

    dot schrieb:

    Und wenn du 100000 Objekte hast die alle auf einen anderen Logger loggen können müssen hast du sowieso ganz andere Probleme...

    Wieso auf einen anderen, du hast auch beim gleichen Logger 1000000 mal den gleichen Pointer umsonst gespeichert.



  • WartungsProgrammierer schrieb:

    Okay, wie sieht denn dann das typische state-of-the-art c++ Beispiel aus?

    Falls die Frage an mich gerichtet war: Keine Ahnung. volkard empfiehlt Modern C++ Design zu lesen.

    MfG SideWinder



  • bewertung schrieb:

    Eigentlich schon, wenn man eine createInstance Methode einführt. Dann fallen auch ein paar andere Probleme wie Initialisierungsreihenfolge weg.

    Um das Problem mit der Initialisierungsreihenfolge zu lösen wird man ein Meyers Singleton verwenden. Das bedeutet schonmal ein (zumindest implizites) if am Beginn deiner getInstance() Methode. Aber es kommt noch schlimmer, denn getInstance() muss ja auch Thread-Safe sein. Ein naiver Ansatz wird dabei evtl. ein Mutex verwenden, d.h. jeder getInstance() Aufruf bedeutet ein Lock acquiren. Eine bessere Lösung wird Double Checked Locking verwenden, aber auch da brauchts mindestens ein if und eine Memory-Barrier. Erzähl mir jetzt nicht dass das performanter ist als einfach über eine Referenz zuzugreifen. Abgesehen davon sind diese Probleme die "wegfallen" alles Probleme die mach sich durch die Verwendung von Singleton überhaupt erst eingefangen hat. Mit DI ist es schon systembedingt unmöglich dass es jemals zu einer undefinierten Initialisierungsreihenfolge kommen könnte.

    bewertung schrieb:

    dot schrieb:

    Und wenn du 100000 Objekte hast die alle auf einen anderen Logger loggen können müssen hast du sowieso ganz andere Probleme...

    Wieso auf einen anderen, du hast auch beim gleichen Logger 1000000 mal den gleichen Pointer umsonst gespeichert.

    Der Punkt ist: Wenn du soviele Objekte hast die was loggen müssen dann hast du imo was falsch gemacht. Abgesehen davon hat ja hustbaer schon eine sehr elegante Lösung mit Thread Locals vorgestellt die in so einem Fall evtl. interessant sein könnte.



  • dot schrieb:

    Um das Problem mit der Initialisierungsreihenfolge zu lösen wird man ein Meyers Singleton verwenden. Das bedeutet schonmal ein (zumindest implizites) if am Beginn deiner getInstance() Methode. Aber es kommt noch schlimmer, denn getInstance() muss ja auch Thread-Safe sein. Ein naiver Ansatz wird dabei evtl. ein Mutex verwenden, d.h. jeder getInstance() Aufruf bedeutet ein Lock acquiren. Eine bessere Lösung wird Double Checked Locking verwenden, aber auch da brauchts mindestens ein if und eine Memory-Barrier. Erzähl mir jetzt nicht dass das performanter ist als einfach über eine Referenz zuzugreifen. Abgesehen davon sind diese Probleme die "wegfallen" alles Probleme die mach sich durch die Verwendung von Singleton überhaupt erst eingefangen hat. Mit DI ist es schon systembedingt unmöglich dass es jemals zu einer undefinierten Initialisierungsreihenfolge kommen könnte.

    mindestens ein if mit memory barrier braucht man auch für's serialisieren der anfragen der parallelen threads, die auf den selben logger schreiben wollen. das kann der singleton in einem aufwasch mitwaschen (singleton steht minderstens seit alexandrescu nicht für eine bestimmte implementierung, sondern für ein konzept, das tausende von ausprägungen haben kann, durch kombination der policies, die man hineinschubst). wer die instance hält, ist auch der einzige schreibberechtigte. das klingt sehr günstig unter der annahme, daß der/die zugrundeliegende(n) streams (z.B. ofstream) von sich aus gar nicht threadsafe sind. weil sich das so natürlich ergibt, hätte ich auch nichts dagegen, es zu verwenden. recht einfach und durchaus performant.

    ich will nicht sagen, daß man modern c++ design lesen sollte, und dann noch bessere patentrezepte hat als die GoF liefern. im gegenteil. patentrezepte sind mir nicht koscher. siehe sig. ich sage nicht, man soll immer DI nehmen oder nie oder sowas. ich antworte auch nicht auf die frage, wie man in c++ loggen muß. weil ich es gar nicht weiß.



  • dot schrieb:

    bewertung schrieb:

    Eigentlich schon, wenn man eine createInstance Methode einführt. Dann fallen auch ein paar andere Probleme wie Initialisierungsreihenfolge weg.

    Um das Problem mit der Initialisierungsreihenfolge zu lösen wird man ein Meyers Singleton verwenden. Das bedeutet schonmal ein (zumindest implizites) if am Beginn deiner getInstance() Methode. Aber es kommt noch schlimmer, denn getInstance() muss ja auch Thread-Safe sein. Ein naiver Ansatz wird dabei evtl. ein Mutex verwenden, d.h. jeder getInstance() Aufruf bedeutet ein Lock acquiren. Eine bessere Lösung wird Double Checked Locking verwenden, aber auch da brauchts mindestens ein if und eine Memory-Barrier. Erzähl mir jetzt nicht dass das performanter ist als einfach über eine Referenz zuzugreifen. Abgesehen davon sind diese Probleme die "wegfallen" alles Probleme die mach sich durch die Verwendung von Singleton überhaupt erst eingefangen hat. Mit DI ist es schon systembedingt unmöglich dass es jemals zu einer undefinierten Initialisierungsreihenfolge kommen könnte.

    Stellst du dich so dumm, weil du DI als Patentlösung ansiehst und alles andere als schlecht darstellen willst oder verstehst du wirklich nicht warum ich createInstance schreibe? 🙄 Die createInstance Methode erstellt das Ding und wird einmal am Anfang aufgerufen, ähnlich wie du es mit DI machst. getInstance macht nur noch return m_instance, vielleicht noch ein assert, sonst nix.


Anmelden zum Antworten