Singleton verwenden oder nicht?



  • Artchi schrieb:

    Was willst du denn erreichen? Du willst einen Logger... aber das Problem, weshalb du an einen Singlton denkst, will sich mir nicht erschließen. Welches Problem sollte er nochmal lösen?

    Naja ich möchte aus beliebigen Funktionen unterschiedlicher Objekte z.B. eine Log-Funktion aufrufen. Bin dann irgendwann auf einen Singleton gestoßen und war mir nicht sicher, ob es das richtige dafür ist. Scheint aber nicht zu sein. Nun werde ich es wohl über Vererbung lösen. ...ist subjektiv empfunden wesentlich besser.
    Wie gesagt... die Entscheidung auf Singleton fiel nur Aufgrund meiner Erfahrungslosigkeit.

    @all: Danke für eure Hilfe 🙂



  • Du willst unterschiedliche Logger-Typen haben, und du weißt zur Laufzeit nicht, welchen Typ bzw. willst das nicht konkret wissen?
    Fabrik-Methode wäre eine Möglichkeit.
    Anstatt Circle, Rectangle usw. machste unterschiedliche Logger:
    http://www.kharchi.eu/?p=885



  • SBond schrieb:

    Naja ich möchte aus beliebigen Funktionen unterschiedlicher Objekte z.B. eine Log-Funktion aufrufen.

    Manchmal ist es durchaus OK irgendwo einfach printf reinzuschreiben. Oder ne statische/globale "theLogger" Variable zu verwenden. Nicht alles muss immer 100% flexibel sein. Was in deinem Fall angebracht ist, kann man aus der Ferne natürlich nicht bestimmen.

    SBond schrieb:

    Nun werde ich es wohl über Vererbung lösen. ...ist subjektiv empfunden wesentlich besser.

    Wo/wie soll Vererbung die hier weiterhelfen? Ich ahne Schreckliches...

    Davon abgesehen würde ich dir raten mal folgende Begriffe zu googeln und dich darüber schlau zu lesen:
    * Inversion of Control
    * Dependency Injection
    * Service Locator



  • temi schrieb:

    Skym0sh0 schrieb:

    Ein Argument gegen Singleton(s) sind die versteckten Abhängigkeiten.

    Nehmen wir an wir haben diese Klasse, am Konstruktor sieht man nun, dass diese Klasse Abhängigkeiten zu den beiden Interfaces hat (egal ob das nun Interfaces, abstrakte Klassen oder Konkrete Klassen sind).

    class Foo
    {
    public:
    	Foo(Interface1 const& i1, Interface1 const& i2);
    
    	void bar();
    };
    

    Wenn nun in der Implementierung sowas steht:

    void Foo::bar()
    {
    	i1.someFoo();
    	i2.someMethod();
    
    	Interface3::getInstance().someStuff();
    }
    

    Dann ist diese Abhängigkeit von aussen durch das Interface der Foo Klasse nicht sichtbar, sondern nur wenn man in die Implementierung schaut. Und es reicht ja, wenn diese Singleton Methode nichts an ihrem internen State ändern, sondern nur was zurückgibt z.B., trotzdem ist diese Abhängigkeit da aber versteckt.

    Was hat das jetzt speziell mit Singleton zu tun? Interface3 könnte eine x-beliebige Klasse mit statischen Methoden sein. Es könnte sogar eine beliebige Klasse mit nicht-statischen Methoden sein, um eine versteckte Abhängigkeit zu bewirken.

    Ups, das stimmt natürlich und bringt die gleichen Probleme mit sich.

    Artchi schrieb:

    Skym0sh0 schrieb:

    Dann ist diese Abhängigkeit von aussen durch das Interface der Foo Klasse nicht sichtbar, sondern nur wenn man in die Implementierung schaut. Und es reicht ja, wenn diese Singleton Methode nichts an ihrem internen State ändern, sondern nur was zurückgibt z.B., trotzdem ist diese Abhängigkeit da aber versteckt.

    Ich stehe ehrlich gesagt auf dem Schlauch. Was ist die schlimme Folge, von dem was du im Code gezeigt hast? 😕

    Skym0sh0 schrieb:

    Jetzt kann man diskutieren, ob das das Verhalten oder die Nutzung einer globalen Variable ist oder nicht. Wenn nicht, stellt sich mir aber die Frage, was denn dann eine globale Variable ausmacht.

    Das Singleton Pattern hat nichts mit Globale Variable zu tun. Es ist keine Alternative und auch keine bessere globale Variable. Das Singleton Pattern soll von einer Klasse erlauben nur EIN Objekt instanzieren zu können. Es geht nicht darum sich zu ersparen, Objekte nicht durchreichen zu müssen. Wer das in einem Singelton Pattern sieht, brockt sich damit natürlich unweigerlich Probleme ein.

    Globale Variablen/Objekte können dagegen von einer Klasse öffters vorkommen!

    Wahrscheinlich ist genau das Problem am Singlton Pattern: es wird oft falsch verstanden und somit falsch eingesetzt. Ist aber nicht die Schuld vom Pattern. :p 🙂

    Vor einiger Zeit hat ein Prof mal erzählt, dass eine Implementierung des Singleton Patterns wäre, dass der Konstruktor eines Objektes bei mehrfacher Instanziierung eine Exception werfen soll. Ich konnte damals nur doof lachen und dachte "Nimm doch einfach die statische Methode". Heute sieht das anders aus, dafinde ich die Lösung gar nicht mehr sooo lachhaft... 🙄



  • hustbaer schrieb:

    Davon abgesehen würde ich dir raten mal folgende Begriffe zu googeln und dich darüber schlau zu lesen:
    * Inversion of Control
    * Dependency Injection
    * Service Locator

    Ist der Service Locator nicht auch ein wenig böse?

    Ich sehe da gerade zwei Möglichkeiten

    1. Service Locator hat statische Methoden => sieht irgendwie aus wie das missbräuchlich verwendete Singleton, nennt sich nur anders.
    2. Service Locator wird der benutzenden Klasse per Dependency Injection bekannt gemacht (z.B. im ctor). Jetzt weiß der Benutzer (oder Unittester) zwar, dass die Klasse einen Service Locator benötigt, aber nicht welche Services daraus.

    Lieg ich damit falsch?

    Gruß,
    temi



  • Ja ja, alles böse. Nur die komplexen Sachen sind gut. Service Locator, der sehr einfach und logisch ist, ist böse. 🙄



  • Artchi schrieb:

    Ja ja, alles böse. Nur die komplexen Sachen sind gut. Service Locator, der sehr einfach und logisch ist, ist böse. 🙄

    Ich will dir ja nicht zu nahe treten, aber keine Antwort ist besser also so eine dumme Antwort.



  • temi schrieb:

    Ist der Service Locator nicht auch ein wenig böse?

    Zumindest gibt es Leute die das ähnlich sehen wie Du:
    http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/



  • temi schrieb:

    1. Service Locator hat statische Methoden => sieht irgendwie aus wie das missbräuchlich verwendete Singleton, nennt sich nur anders.

    Das würde ich persönich nicht unbedingt als Service Locator bezeichnen.

    temi schrieb:

    2. Service Locator wird der benutzenden Klasse per Dependency Injection bekannt gemacht (z.B. im ctor). Jetzt weiß der Benutzer (oder Unittester) zwar, dass die Klasse einen Service Locator benötigt, aber nicht welche Services daraus.

    Korrekt.
    Alles hat Vor- und Nachteile. Man muss ja auch keinen Service Locator verwenden. Ist halt ein Kompromiss den man machen kann, wenn es mal wirklich viele Services gibt, die an vielen Stellen benötigt werden.



  • scrontch schrieb:

    Zumindest gibt es Leute die das ähnlich sehen wie Du:
    http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/

    Der Artikel ist ... teilweise fragwürdig. Erstmal finde ich den Strohmann komisch den er aufbaut, indem er einen globalen und generischen Service Locator verwendet und dann kritisiert. Also erstmal die denkbar schlechteste Variante wählt, um diese dann auseinanderzunehmen.

    Im Verlauf des Artikels wird dann zwar ein Service Locator Objekt daraus, aber es bleibt weiterhin generisch ( locator.Resolve<FooService>() statt locator.GetFooService() oder auch einfach locator.FooService ).

    Das kann man so machen, muss man aber nicht.

    Natürlich bleibt das grundsätzliche Problem: selbst wenn man einen spezialisierten Service Locator in seinem Programm verwendet, wird es viele Klassen bzw. Funktionen geben die nur einen Teil der Services benötigen die dieser spezialisierte Service Locator "kennt". Und sobald man anfängt viele verschiedene spezialisierte Service Locator für verschiedene Programmteile zu bauen, verliert man einen der grossen Vorteile des Service Locators. Nämlich dass man ihn "einfach so" als einen einzigen Parameter rumreichen kann, statt zig verschiedene Services rumzureichen.

    Genau das ist mMn. der Tradeoff den man eingeht: Convenience bei der Implementierung vs. klarere Abhängigkeiten.

    Andrerseits hat man durch den Service Locator auch andere Vorteile. z.B. verleitet klassische Injection dazu zu ... "schlampen". Also angenommen man braucht irgendwo im "mittleren" Bereich des Callstacks auf einmal ein neues Service. Jetzt kann man brav alle Konstruktoren/Init-Funktionen/Interfaces erweitern, so dass man dieses neue Service von "ganz aussen" (=ganz unten im Callstack) mitgeben kann. Das ist oft richtig viel Arbeit. Viel einfacher ist es da schnell mal selbst new zu sagen, und damit die Kette zu brechen. Mit einem Service Locator ist es dagegen viel weniger Aufwand es "korrekt" zu machen. Eben gerade weil die Dependency nicht an 100 Stellen immer und immer wieder explizit sichtbar ist.



  • hustbaer schrieb:

    SBond schrieb:

    Naja ich möchte aus beliebigen Funktionen unterschiedlicher Objekte z.B. eine Log-Funktion aufrufen.

    Manchmal ist es durchaus OK irgendwo einfach printf reinzuschreiben. Oder ne statische/globale "theLogger" Variable zu verwenden. Nicht alles muss immer 100% flexibel sein. Was in deinem Fall angebracht ist, kann man aus der Ferne natürlich nicht bestimmen.

    SBond schrieb:

    Nun werde ich es wohl über Vererbung lösen. ...ist subjektiv empfunden wesentlich besser.

    Wo/wie soll Vererbung die hier weiterhelfen? Ich ahne Schreckliches...

    Davon abgesehen würde ich dir raten mal folgende Begriffe zu googeln und dich darüber schlau zu lesen:
    * Inversion of Control
    * Dependency Injection
    * Service Locator

    Nochmals danke an alle. Ich habe mich in den letzen Wochen intensiver mit Entwurfsmustern beschäftigt und der Imput bzw. der Umfang hat mich doch etwas erschlagen. Mein Verständnis ist zumindest größer als vorher, auch wenn ich noch nicht alles im Detail verstanden habe. Das Loggen werde ich wohl mit Dependency Injection (DI) lösen. Es scheint eine saubere Möglichkeit zu sein, insbesondere da das Testen der Software auch nochmal ein größeres Kapitel sein wird.

    Nun habe ich nochmal eine Frage zu DI und Logging, da ich an einer Stelle noch Probleme sehe. Neben den ganzen Klassen in denen ich DI anwenden kann, habe ich auch noch globale Funktionen, die nur über einen Namespace gruppiert sind. Die Funktionen benötigen keine Membervariablen oder ähnliches, wesshalb ich diese auch nicht in irgendwelchen Klassen gekapselt habe. Die Funktionen nutzen allerdings auch die Logfunktion (derzeit über ein rudimentäres Makro gelöst). Was kann ich hier machen?

    Jede Funktion ein Logger-Objekt als Parameter zu übergeben scheint mir nicht praktikabel. Ich könnte die ganzen Funktionen als Klasse kapseln und dann über den Konstruktor den Logger übergeben. Ist das eine Lösung? 😕

    viele Grüße,
    SBond


Anmelden zum Antworten