Datenbankzugriffsschicht ohne mit Singletons um sich zu schmeißen?



  • Und wer garantiert dir dann, daß createInstance() aufgerufen wird, bevor der erste Anwender versucht, sich mit getInstance() das Singleton-Objekt zu holen?



  • bewertung schrieb:

    Stellst du dich so dumm, weil du DI als Patentlösung ansiehst und alles andere als schlecht darstellen willst [...]

    Ich seh DI nicht als Lösung für alles, absolut nicht. Mir gehts hier darum aufzuzeigen warum Singleton eine schlechte Idee ist und nicht darum DI als goldenen Hammer anzupreisen...



  • CStoll schrieb:

    Und wer garantiert dir dann, daß createInstance() aufgerufen wird, bevor der erste Anwender versucht, sich mit getInstance() das Singleton-Objekt zu holen?

    Der Anwender. Sonst gibts halt ein assert oder Crash. Ich kann auch bei DI Müll programmieren, wenn ich NULL übergebe oder zwei "Singletons" erstelle...



  • Also gut, sagen wir mal ich will deinen Singleton für einen Logger verwenden und unter anderem die Konstruktion meiner Objekte loggen lassen.

    a) wo muß ich den createInstance()-Aufruf unterbringen, damit ein globales Objekt erfolgreich (d.h. ohne assert/Crash) dieser Klasse angelegt werden kann.
    b) Wer ist für den createInstance()-Aufruf zuständig, wenn mehrere Klassen diesen Logger verwenden wollen? Und was passiert, wenn der Aufruf mehrfach passiert?

    PS: Ich will hier nicht die Vorteile der DI verteidigen, dazu kenne ich das Konzept zu wenig.



  • bewertung schrieb:

    Ich kann auch bei DI Müll programmieren, wenn ich NULL übergebe [...]

    Müll programmieren kann man immer und überall, vor allem in C++. Aber es ist gerade einer der großen Vorteile von DI dass es ziemlich schwer wird. Das System macht sämtliche Abhängigkeiten explizit, du kannst Objekte gar nicht erst erzeugen ohne essentielle Abhängigkeiten bereitzustellen, d.h. die korrekte Initialisierungsreihenfolge wird schon zur Compiletime forciert. Wenn ein Objekt unbedingt ein anderes braucht dann verlangt es eine Referenz und keinen Pointer. Damit kannst du nicht einfach 0 übergeben (ohne undefiniertes Verhalten in kauf zu nehmen).

    bewertung schrieb:

    [...] oder zwei "Singletons" erstelle...

    Was genau meinst du damit!?



  • CStoll schrieb:

    PS: Ich will hier nicht die Vorteile der DI verteidigen, dazu kenne ich das Konzept zu wenig.

    Doch kennst du, ich versteh blos nicht, dass es unter eigenen Begiff gehypt wird 😉

    http://tutorials.jenkov.com/dependency-injection/index.html



  • Zeus schrieb:

    Doch kennst du, ich versteh blos nicht, dass es unter eigenen Begiff gehypt wird 😉

    Ja, ich wusste bis vor kurzem auch nicht dass es einen Namen dafür gibt. Mich hats auch gewundert dass es da offenbar einen gewissen Hype drum gibt, ich dachte eigentlich dass das für jeden vernünftigen Programmierer die natürliche Vorgehensweise wäre. Aber seit ich es weiß verwend ich den Namen auch, hat den Vorteil dass sofort klar ist was gemeint ist...



  • CStoll schrieb:

    Also gut, sagen wir mal ich will deinen Singleton für einen Logger verwenden und unter anderem die Konstruktion meiner Objekte loggen lassen.

    a) wo muß ich den createInstance()-Aufruf unterbringen, damit ein globales Objekt erfolgreich (d.h. ohne assert/Crash) dieser Klasse angelegt werden kann.
    b) Wer ist für den createInstance()-Aufruf zuständig, wenn mehrere Klassen diesen Logger verwenden wollen?

    Relativ am Anfang von main, so ähnlich wie wenn du ihn mit DI überall hin weitergeben willst.

    Und was passiert, wenn der Aufruf mehrfach passiert?

    Assert, crash, nix, was man halt in die createInstance() rein macht.

    dot schrieb:

    bewertung schrieb:

    [...] oder zwei "Singletons" erstelle...

    Was genau meinst du damit!?

    // main
    Logger loggerDerEigentlichDerEinzigeSeinSoll.
    foo(loggerDerEigentlichDerEinzigeSeinSoll)
    
    //... viel Code 
    //... ganz anderer Klasse
    
    Logger loggerDerZweite.
    foo(loggerDerZweite)
    


  • Mit DI werden die Objekte im Normalfall von einem Framework erzeugt und nicht von dir selbst, dementsprechend wirst du nie unabsichtlich zwei erzeugen.

    MfG SideWinder



  • bewertung schrieb:

    dot schrieb:

    bewertung schrieb:

    [...] oder zwei "Singletons" erstelle...

    Was genau meinst du damit!?

    // main
    Logger loggerDerEigentlichDerEinzigeSeinSoll.
    foo(loggerDerEigentlichDerEinzigeSeinSoll)
    
    //... viel Code 
    //... ganz anderer Klasse
    
    Logger loggerDerZweite.
    foo(loggerDerZweite)
    

    Ähm, ich hoffe dir ist klar dass die Tatsache dass genau das problemlos möglich ist doch gerade einer der wesentlichen Vorteile von DI ist!? Abgesehen davon seh ich da keine Singletons...



  • bewertung schrieb:

    CStoll schrieb:

    Also gut, sagen wir mal ich will deinen Singleton für einen Logger verwenden und unter anderem die Konstruktion meiner Objekte loggen lassen.

    a) wo muß ich den createInstance()-Aufruf unterbringen, damit ein globales Objekt erfolgreich (d.h. ohne assert/Crash) dieser Klasse angelegt werden kann.
    b) Wer ist für den createInstance()-Aufruf zuständig, wenn mehrere Klassen diesen Logger verwenden wollen?

    Relativ am Anfang von main, so ähnlich wie wenn du ihn mit DI überall hin weitergeben willst.

    Dir ist klar, daß globale Objekte angelegt werden, bevor du in die main() kommst? Und daß der Autor der main()-Funktion möglicherweise gar nicht weiß, daß ich tief in den Innereien meiner Library den Singleton-Logger verwende?

    Und was passiert, wenn der Aufruf mehrfach passiert?

    Assert, crash, nix, was man halt in die createInstance() rein macht.

    Also muß diese Funktion genauso geschützt werden wie die getInstance() bei einem "normalen" Singleton. Aber dadurch, daß sie explizit aufgerufen wird, vergrößerst du nur die Probleme des Singleton, ohne damit einen erkennbaren Nutzen zu bieten.

    @Zeus/dot: Kann schon sein, daß ich das bereits unbewußt angewendet habe. Darüber muß ich noch etwas meditieren.



  • SideWinder schrieb:

    Mit DI werden die Objekte im Normalfall von einem Framework erzeugt und nicht von dir selbst, dementsprechend wirst du nie unabsichtlich zwei erzeugen.

    MfG SideWinder

    dot schrieb:

    bewertung schrieb:

    dot schrieb:

    bewertung schrieb:

    [...] oder zwei "Singletons" erstelle...

    Was genau meinst du damit!?

    // main
    Logger loggerDerEigentlichDerEinzigeSeinSoll.
    foo(loggerDerEigentlichDerEinzigeSeinSoll)
    
    //... viel Code 
    //... ganz anderer Klasse
    
    Logger loggerDerZweite.
    foo(loggerDerZweite)
    

    Ähm, ich hoffe dir ist klar dass die Tatsache dass genau das problemlos möglich ist doch gerade einer der wesentlichen Vorteile von DI ist!? Abgesehen davon seh ich da keine Singletons...

    Sell dich nicht so...?!?!?!? 🙄

    CStoll schrieb:

    bewertung schrieb:

    CStoll schrieb:

    Also gut, sagen wir mal ich will deinen Singleton für einen Logger verwenden und unter anderem die Konstruktion meiner Objekte loggen lassen.

    a) wo muß ich den createInstance()-Aufruf unterbringen, damit ein globales Objekt erfolgreich (d.h. ohne assert/Crash) dieser Klasse angelegt werden kann.
    b) Wer ist für den createInstance()-Aufruf zuständig, wenn mehrere Klassen diesen Logger verwenden wollen?

    Relativ am Anfang von main, so ähnlich wie wenn du ihn mit DI überall hin weitergeben willst.

    Dir ist klar, daß globale Objekte angelegt werden, bevor du in die main() kommst? Und daß der Autor der main()-Funktion möglicherweise gar nicht weiß, daß ich tief in den Innereien meiner Library den Singleton-Logger verwende?

    Dann würde auch die von dot verwendete Constructor-Injection nicht gehen.

    Und was passiert, wenn der Aufruf mehrfach passiert?

    Assert, crash, nix, was man halt in die createInstance() rein macht.

    Also muß diese Funktion genauso geschützt werden wie die getInstance() bei einem "normalen" Singleton. Aber dadurch, daß sie explizit aufgerufen wird, vergrößerst du nur die Probleme des Singleton, ohne damit einen erkennbaren Nutzen zu bieten.

    Nutzen siehe oben.

    Ich hab jetzt keine Lust mehr alles dreimal zu erklären.



  • bewertung schrieb:

    Sell dich nicht so...?!?!?!? 🙄

    😕

    bewertung schrieb:

    Dann würde auch die von dot verwendete Constructor-Injection nicht gehen.

    Wo liegt das Problem mit der Injection?

    bewertung schrieb:

    Ich hab jetzt keine Lust mehr alles dreimal zu erklären.

    Geh doch einfach auf die genannten Punkte ein...



  • bewertung schrieb:

    Dir ist klar, daß globale Objekte angelegt werden, bevor du in die main() kommst? Und daß der Autor der main()-Funktion möglicherweise gar nicht weiß, daß ich tief in den Innereien meiner Library den Singleton-Logger verwende?

    Dann würde auch die von dot verwendete Constructor-Injection nicht gehen.

    Wieso das? In meiner Library kann ich machen, was ich will, also kann ich auch sowas schreiben:

    Logger myLog(...);
    MyClass Worker(myLog);
    

    Da muß ich mich nicht davon abhängig machen, daß irgendjemand anderes (der vielleicht gar nichts von der inneren Arbeitsweise meines Codes weiß) mir einen Logger zur Verfügung stellt bzw. initialisiert?

    Und was passiert, wenn der Aufruf mehrfach passiert?

    Assert, crash, nix, was man halt in die createInstance() rein macht.

    Also muß diese Funktion genauso geschützt werden wie die getInstance() bei einem "normalen" Singleton. Aber dadurch, daß sie explizit aufgerufen wird, vergrößerst du nur die Probleme des Singleton, ohne damit einen erkennbaren Nutzen zu bieten.

    Nutzen siehe oben.

    Der Nutzen besteht darin, daß die getInstance()-Methode sich verkürzt und keine Synchronisation etc benötigt, habe ich das richtig verstanden?
    Dafür benötige ich jetzt eine andere Methode, die ihre Synchronisation- und ähnlichen Probleme erbt - und muß auch noch dafür sorgen, daß jemand anderes diese Methode aufruft. Irgendwie habe ich das Gefühl, daß da die Nachteile überwiegen.
    (wobei ich nichtmal ein Verfechter von "normalen" Singletons bin - ich bin schon oft genug über global gültige Variablen in den unterschiedlichsten Verkleidungen gestolpert)



  • Zeus schrieb:

    CStoll schrieb:

    PS: Ich will hier nicht die Vorteile der DI verteidigen, dazu kenne ich das Konzept zu wenig.

    Doch kennst du, ich versteh blos nicht, dass es unter eigenen Begiff gehypt wird 😉

    Gehypt wird glaub ich mehr DI mit Frameworks und entsprechenden Konfigurationsmöglichkeiten und nicht die simple Konstruktor-Injection die dot hier so hypt.



  • Gibt es hier eigentlich irgendjemand, außer dot, der Logger und DB manuell per Konstruktor-Injection weiter gibt, oder verwendet ihr alle DI Frameworks dazu?



  • In den C#/ASPX Projekten (habe ich Anfang des Jahres von einem Ex-Kollegen geerbt) habe ich Framework-basierte DI (d.h. durch Austausch/Bearbeiten einer XML-Datei kann ich die verwendeten Klassen austuaschen und Parameter setzen).
    In den SAL-Projekten, die die überwiegende Mehrheit bei uns ausmachen, läuft es wohl auf eine Art Header-basierte DI hinaus (auch wenn es dort keine Trennung zwischen Header und Implementationsdatei gibt).
    C++ habe ich seit einer Weile nicht mehr produktiv und im größeren Stil verwendet.

    (Randfrage: Der Umgang der STL mit Allokatoren und Traits-Klassen dürfte doch auch in den Bereich DI fallen, oder?)



  • Ich verstehe gar nicht, wieso hier um createInstance gestritten wird, kann man nicht auch so die if-Abfrage sparen und Threadsicherheit erzeugen:

    class TestSingleton
    {
    private:
    	static TestSingleton st;
    
    	int z;
    
    	TestSingleton() {z = 100;}
    	TestSingleton(const TestSingleton&);
    
    public:
    	static TestSingleton& getInstance()
    	{
    		return st;
    	}
    
    	int getZ() const {return z;}
    };
    
    TestSingleton TestSingleton::st ;
    

    Das kompiliert bei mir problemlos. Nachteil ist, dass es immer instanziiert wird und nicht bei Verwendung. Aber je nach Anwendungszweck finde ich das nicht so dramatisch.



  • Eisflamme schrieb:

    Ich verstehe gar nicht, wieso hier um createInstance gestritten wird, kann man nicht auch so die if-Abfrage sparen und Threadsicherheit erzeugen:

    Thread-Sicherheit: Ja, das Problem bei diesem Ansatz ist die Initialisieurngs-Reihenfolge von globalen Objekten.



  • Hm, verstehe... Und so was?

    class TestSingleton
    {
    private:
    	static TestSingleton st;
    
    	int z;
    
    	TestSingleton() {z = 100; std::cout << "Singleton initialized.";}
    	TestSingleton(const TestSingleton&);
    
    	friend TestSingleton& getSingletonInstance();
    
    public:
    	int getZ() const {return z;}	
    };
    
    TestSingleton& getSingletonInstance()
    {
    	static TestSingleton ts;
    	return ts;
    }
    

    Hm und die Übergabe von Traits und Allokatoren passt für DI imo nicht ganz, weil man nur Klassen, aber keine Objekte übergibt. Großer Vorteil der DI ist aber, dass man die Initialisierung dem Aufrufer überlässt. Man löst dadurch eher die dynamische Polymorphie ab, finde ich.


Anmelden zum Antworten