Datenbankzugriffsschicht ohne mit Singletons um sich zu schmeißen?



  • Es gibt eine IMO gute Alternative zu Singletons, die sehr selten erwähnt wird. Was mich etwas erstaunt. Und das sind Thread-Locals.

    Die eignen sich z.B. auch sehr schön für Logging.

    Dadurch kann man nach wie vor über Makros und ohne irgendwas explizit rumreichen zu müssen auf den Logger zugreifen.

    Gleichzeitig kann man aber für verschiedene Dinge verschiedene Logger verwenden, im selben Programm. Und auch so, dass eine Funktion F verschiedene Logger verwendet, abhängig davon zu welchem Zweck F gerade aufgerufen wird. Ohne dass man den Logger an Funktion F explizit übergeben müsste.

    Wenn Thread X anfängt Request A zu bearbeiten, kann er nen Zeiger auf den passenden Logger in die Thread-Local Variable schreiben. Wenn Funktion F dann einen Logger braucht guckt sie einfach in der Thread-Local Variable nach welchen sie verwenden soll.

    Wenn Thread X aufhört Request A zu bearbeiten, setzt er den Zeiger wieder zurück.

    Und wenn gleichzeitig in Thread Y Request B bearbeitet wird, wo vielleicht ein ganz anderer Logger verwendet werden sollte, gibt es auch kein Problem.

    Achja, mit "Logger" meine ich jetzt nicht notwendigerweise das Ding das die Log-Message in ein File schreibt. Kann auch gerne ein "Log-Channel" sein, der die Message erstmal filtert und dann ggf. an mehrere "Log-Writer" weiterreicht.

    ----

    Fail-safe gegenüber Festplatten-, Netzwerk- und Programmfehler (= Absturz)?

    Newsflash: das geht nicht.

    Um nur mal ein paar Stichpunkte zu nennen die nicht-Bananensoftware eigentlich zu Kenntnis nehmen sollte.

    Du übertreibst auch gar nicht gerne, oder?



  • volkard schrieb:

    SideWinder schrieb:

    Ich habe den Thread nicht verfolgt, aber generell, aus Sicht der derzeitigen SE-Theorie, ist ein Singleton hier falsch. Überall herumzureichen auch. Dafür verwendet man im Normalfall Dependency Injection. Keine Ahnung ob sich das schon bis C++ herumgesprochen hat.

    Alexandrescus Modern C++ Design zeigt für C++ angemessene Techniken, die mit Templates Inversion of Control erlauben und damit typsicher sind und keinen Laufzeitoverhead kosten. Wir benutzen entsprechend Dependency Injection nebst DI-Framework eher nicht. Und wir haben eine mächtig große Abneigung dagegen, Logik in Konfigurationsdateien zu stecken.

    Ja, wenn ich C++ professionell einsetzen würde, wäre das wohl auch eines der ersten Bücher die ich mir sofort zulege. Das verwechseln leider sehr viele bei Vergleichen zwischen C++ und Java/C#, dass die beiden Sprachen bis auf die Syntax und das Keyword "objektorientiert" nicht gar so viel gemeinsam haben. Mit "herumgesprochen" meinte ich jetzt nichts Böses. Wenn ich jedoch mit C++-Code in Berührung komme (nicht oft, und auch nicht hochprofessionell), dann aber leider meistens in Form von Templates=Generics und ansonsten eher Tooling + SE aus den 90ern, deswegen die Formulierung. Also schon etwas böse 🤡 Aber keine Ahnung wie das aussieht.

    Inwiefern ich Logik in eine Konfigurationsdatei verstecke wenn ich dort nur festlege welche Implementierung eines Interfaces ich wähle? Hmm, ich weiß nicht inwiefern ich da Logik verstecke, aber gut.

    MfG SideWinder



  • Das "ver" von "verstecke" habe ich gar nicht gesagt.



  • volkard schrieb:

    Das "ver" von "verstecke" habe ich gar nicht gesagt.

    Irgendwie vermutete ich unterbewusst, dass das ein Grund dagegen sein könnte. Ich glaube aber weiterhin, dass ich gar keine Logik in die Konfigurationsdatei stecke, dementsprechend also auch nichts verstecke.

    MfG SideWinder



  • dot schrieb:

    fdfdg schrieb:

    Magst du mal ein konkretes Beispiel geben, wie so ein Dependency Injection Logger aussehen könnte?

    class Log
    {
    private:
      Log(const Log&);
      Log& operator =(const Log&);
    protected:
      Log() {}
      ~Log() {}
    public:
      virtual void message(const std::string& msg) = 0;
      virtual void warning(const std::string& msg, int level) = 0;
      virtual void error(const std::string& msg, int level) = 0;
    };
    

    😕

    Kann es sein, dass du Dependency Injection mit Interfaces verwechselst, dass Beispiel bringt mal garnichts.



  • Mach doch mal ein Beispiel, wo Klasse A in ein Logfile schreibt, Klasse B in ein Logfile und das Logwindow schreibt und Funktion C in ein Logfile und auf Console loggt.



  • DI wurde doch schon ausführlich diskutiert. Jedes Objekt das was loggen will bekommt nun eben so ein Interface!? Das Objekt welches ich übergeb kann nun in ein Logfile oder ein Logwindow oder was auch immer schreiben...



  • 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


Anmelden zum Antworten