Gebruik geen singletons!

 
06 januari 2011

Vroeger was ik altijd erg happig om singletons te gebuiken. Immers, het leek mij logisch om iets te hebben zoals Database.GetInstance().DoQuery(query); want ik heb namelijk maar één database en mijn applicatie heeft maar één verbinding met die database. Toch is het gebruik van singletons problematisch. Het is namelijk een verkapte vorm van een global state, iets wat je niet wilt hebben. Je code wordt moeilijker testbaar, en wordt moeilijker om te onderhouden.

Als je een singleton moet hebben om overal ergens bij te kunnen, dan duidt dat op een code smell. Waarschijnlijk is je code niet goed losgekoppeld en moet je gaan nadenken over een beter ontwerp. Waarom heb je een sterke koppeling? Omdat je singleton je afhankelijkheden verstopt.

Bij het gebruik van singletons gaat je API onwaarheden vertellen. Singletons zorgen ervoor dat je interfacedefinities patheologische leugenaars worden. Een voorbeeld:

1

In principe zou de bovenstaande code moeten werken. Echter, bij het uitvoeren van de test krijg je een NullReferenceException. Waarom? Omdat er een singleton binnen de methode Buy aangeroepen wordt dat nog niet geïnitieerd is:

2

De OrderService is dus een verborgen afhankelijkheid dat niet gedocumenteerd is in de interface. Vanuit de interface wordt het niet duidelijk dat er samengewerkt wordt met een OrderService. Ik kan nu ook niet testen zonder een OrderService te initialiseren. Ik moet dus in mijn test OrderService.Setup() aanroepen om mijn test werkend te krijgen. Misschien wil ik mijn OrderService ook wel mocken om te testen, zodat ik niet steeds een email krijg met het bericht dat ik een product gekocht heb.

Daarnaast kan iedereen bij de OrderService, dus als ik meerdere tests naast elkaar draai, kan het zijn dat ze invloed op elkaar hebben. Dit noemen we ook wel “spooky action at a distance”. Hoe kunnen we dit verbeteren? Bijvoorbeeld met dependency injection. Dan denken mensen misschien dat je dan meteen een duur Inversion-of-Control framework nodig hebt, maar niets is minder waar. Ik kan mijn afhankelijkheid simpelweg in de constructor definiëren:

3

De volgorde van statements is nu oninteressant, een klant kan altijd dingen kopen, omdat hij niet kan bestaan zonder OrderService. Nu heb ik de afhankelijkheid expliciet gemaakt. Ook kan ik nu een mock OrderService injecteren om te testen.

De code wordt op deze manier minder afhankelijk van de rest van het systeem. Voor nieuwe ontwikkelaars wordt het op basis van de interfacedefinitie snel duidelijk dat ze weinig kunnen beginnen zonder een OrderService. Anderzijds was het een proces van vallen en opstaan om te bepalen wat precies de functionaliteit van een klasse is en waarom zaken zich anders gedragen dan verwacht.

Het kan echter zo zijn dat je toch bepaalde zaken applicatiebreed wilt delen en waarschijnlijk dan ook maar één instantie van is. Een object zou in principe niet zijn eigen lifecycle mogen beheren middels static velden. Bij web applicaties die draaien op een webserver zoals IIS wordt dit problematisch. IIS ruimt namelijk zo nu en dan zijn worker process op, waardoor je instanties verdwijnen. Eveneens heeft jouw singleton te maken met meerdere HTTP requests en consequente threads die die requests afhandelen. Kan jouw singleton meerdere threads aan? Threads gaan dus door elkaar aan de singleton zitten waardoor de state verandert, wederom spooky action at a distance.

De lifecycle van applicatiebrede objecten wil je dus idealiter laten afhandelen door het framework. In ASP.NET heb je meerdere hooks in het framework zoals bijvoorbeeld een IHttpModule. IHttpModule wordt vaak gebruikt om bijvoorbeeld NHibernate sessies te beheren. Je legt de verantwoordelijkheid van het instanciëren van je object neer bij de verantwoordelijke, namelijk bij het begin van een http request. In een desktop omgeving zou je je applicatiebrede object als member in je mainform kunnen stoppen.

In conclusie, singletons zijn eigenlijk een schending van de regels van Object Georiënteerd Programmeren. OOP stelt dat je alleen met je vrienden praat, maar middels singletons kun je iedereen met iedereen laten praten waardoor je een warboel aan verborgen afhankelijkheden krijgt.


Werken met ?
Kijk dan bij onze mogelijkheden voor zowel starters als ervaren engineers.


Categorieën: Development


Reacties (30)

  • Lekker stellig, goed om de discussie op gang te krijgen. :)

    Ben het wel met je eens. Static classes en methodes zijn soms wel nuttig, maar een singleton heeft juist als taak om iets dat static is, op een object te laten lijken. Eigenlijk is het dus gewoon een wolf in schaapskleren. Beter om dan te zien dat iets static is.

    Daarnaast zijn static classes en methodes een aanwijzing dat het gaat om een cross-cutting concern. Zoals bijvoorbeeld logging, zoals al als voorbeeld gegeven werd. Er zijn mooiere manieren om dat op te lossen, die grotendeels neerkomen op Aspect Oriented Programming, zodat deze zaken niet terugkomen in de context waar ze niet thuishoren (b.v. in een domeinmodel). Maar goed, of het de moeite waard is om dat ook toe te passen is afhankelijk van de complexiteit van je applicatie.

    Geplaatst op 09 januari 2011 om 15:47 Permalink

  • Als je één directeur nodig hebt, dan stop je dat in je root object. Ik kan me de volgende code voostellen:

    class Bedrijf
    {
    private Persoon directeur;

    public Bedrijf(Persoon directeur) { .. }
    }

    class Program
    {
    static Main()
    {
    Bedrijf bedrijf = new Bedrijf(new Persoon(“Dirk”));
    MainForm form = new MainForm(bedrijf);
    form.Show();
    }
    }

    Geplaatst op 07 januari 2011 om 10:19 Permalink

  • Peter schreef:

    Matthijs: het idee om het keyword STATIC af te schaffen… Neen!
    Static gaat misschien enigszins in tegen het pure OO-idee dat een systeem bestaat uit objecten die elkaar kennen. Soms heb je echter algemene functionaliteit nodig, die je mooi in helper classes kunt stoppen. Denk ook aan het .NET Framework zelf, waarin je bijvoorbeeld String.Format of Math.Round aanroept. Zou je dit echt allemaal willen vervangen door constructors en functies van (niet-static) objecten?

    Geplaatst op 07 januari 2011 om 9:39 Permalink

    • Kijk naar Ruby, alles is een object:

      ” “.IsWhiteSpace();

      Iets dergelijks kun je in Ruby doen.

      En dan zou je natuurlijk ook dit kunnen doen:

      int rounded = (11 / 6).Round();

      Dus waarom heb ik static nodig?

      Geplaatst op 07 januari 2011 om 10:23 Permalink

      • Andries Nieuwenhuize schreef:

        var vier = “vier”.Length;
        var ookvier = 4.ToString();

        Welcome to .NET. :-)

        Maar, evenals Peter ben ik ook erg benieuwd waar jij bijvoorbeeld je Math.Round() zou gaan stoppen. Functies dus die niet direct bij één specifiek object horen. Ga je deze functie dan toevoegen aan ieder numeriek type. Of moeten we eerst Math instantiëren (wat dat dan ook zou inhouden) om vervolgens de functie te kunnen gebruiken?

        Rule: Gebruik geen state als je het niet nodig hebt.

        Geplaatst op 07 januari 2011 om 10:32 Permalink

        • var rounded = 1,23333333333.Ceiling();

          Waarom heb ik een static math nodig??

          Geplaatst op 07 januari 2011 om 10:36 Permalink

          • Andries Nieuwenhuize schreef:

            Single Point of Defenition my friend. Ik ben het met je eens, het ziet er mooier uit dan via zo’n (lastig) static Math object. Maar, als wilt dat men bij Microsoft ieder numeriek type moet gaan optuigen met alle mogelijke operaties die daarop uitgevoerd kunnen worden, zullen ze je dat niet in dank afnemen. Stel je eens voor dat de Round functie niet goed is geïmplementeerd. Dan moet men gaan zoeken welke numerieke objecten die allemaal geïmplementeerd hebben. Hadden we de functie Round voor de duidelijkheid ook maar in een Int32 gestopt? Nu hoeven ze enkel het Math object aan te passen.

            Een van de principes van OO is toch code structureren? Help je dat zo niet een beetje om zeep?

            Oja, je kunt natuurlijk extension methods gebruiken om op één plek je functies te definiëren en die op verschillende objecten toe te passen. Maar laten die nu ook static zijn…

            Geplaatst op 07 januari 2011 om 11:03 Permalink

  • Matthijs Mast schreef:

    Andries,

    Ik bedoel dat static variabele (i.i.g. in .NET maar volgens mij ook in c++ en andere native languages) niet verder komen dan uniek binnen het proces. En in .NET binnen het AppDomain. Static variabele dragen niet over proces grenzen heen wat ook logisch is aangezien proces x niks weet van een zekere variabele in proces b.

    Geplaatst op 07 januari 2011 om 9:11 Permalink

  • Andries Nieuwenhuize schreef:

    > Deze zijn namelijk alleen uniek binnen het appdomein.
    Wie zegt dat? Is dat niet concept verwarren met implementatie?

    Je oppert wel een interessante optie: Bestaat er een binnendomeinssingleton en een buitendomeinssingleton? Ik vind dat er geen verschil mag zijn.

    Nu, buitendomeins gezien het volgende:
    1. Goed, stel dat mijn appdomein Windows heet? Dan valt de Diskfragmeter binnen mijn domein. Hoe ga ik één instantie van D-Fragmenter afdwingen? Ga je dat voor ieder applicatie een static private veld maken?
    2. Naar mijn beleving is het gebruiken van een Mutex of lock o.i.d. gewoon valide manieren om een Singleton af te dwingen / implementeren. Zelfde als het bekende Process.GetCurrentProcess() om buiten je eigen applicatiedomein te kijken.

    Binnendomeins:
    > in het voorbeeld van de samenwerkende personen [maakt het] dan ook niets uit hoeveel directeuren er in het geheugen zijn.
    Hmm, loslopende directeuren buitendienst. Gevaarlijk. Want, als ze er nog zijn betekend het dat minimaal nog één object nog aan hun refereert. Ik begrijp jou punt, maar je hebt me niet kunnen overtuigen. :-) Jij legt de verantwoordelijkheid voor 1 directeur bij een hogerliggende klasse: Bedrijf/Vereniging. Ik leg het bij de klasse in kwestie zelf.

    B.t.w. wordt wel een leuke discussie zo!

    Geplaatst op 07 januari 2011 om 8:53 Permalink

  • Matthijs Mast schreef:

    @ Andries:
    “Probeer maar eens meerde instanties van je diskfragmenter op te starten”
    Als ik hiervoor een Singleton zou gebruiken zou ik net zoveel diskfragmenters kunnen opstarten als ik wilde. Deze zijn namelijk alleen uniek binnen het appdomein. Als je zoiets wilt moet je gaan werken met een Mutex of een Event of een andere CrossDomain object.

    Geplaatst op 07 januari 2011 om 7:50 Permalink

  • Matthijs Mast schreef:

    Ik zie dat er een aardige discussie ontstaat en ik kan het niet laten om me erin te mengen.
    Allereerst denk ik dat er maar 1 acceptabele situatie is waarin een Singleton gebruikt kan worden en dat is met loggen. Loggen veranderd niets aan de executie van je programmalogica en moet door practisch iedere klasse gebruikt worden wat dependency injectie ondoenlijk en lelijk maakt. Verder zou ik ten zeerste afraden om Singletons in een domein te stoppen omdat je dan in feite buiten je domein bezig bent. In het geval van Matthijs waar een domeinobject onder water een service aanroept creeer je (zoals Matthijs al zij) een veel te vaste koppeling en bovendien betekend dat dat je domein niet meer kan functioneren als een zelfstandig geheel aangezien er altijd een service nodig is.
    In het geval van Andries (een bedrijf heeft maar een directeur) Dit kun je afdwingen als class Bedrijf{ public Directeur{get;set;}}. Een domein is er om de logische objecten aan te bieden aan een applicatie.Het maakt in het voorbeeld van de samenwerkende personen dan ook niets uit hoeveel directeuren er in het geheugen zijn. Als de personen er allemaal maar 1 kennen. Misschien is de applicatie wel bedoeld om te werken met meerdere bedrijven of verenigingen in het geheugen. Anyway om even terug te komen op Singletons (want daar ging het over). Als je dan per se een Singleton nodig denkt te hebben is het in mijn ervaring het makkelijkst om een static variabele direct te instantieren in de class (zoals je met het log4net log ook doet eigenlijk. Komen we toch weer bij logging uit) zo krijg je i.i.g. niet de nullreference exeptie.Mocht je parameters nodig hebben voor het instantieren dan is het nog steeds niet de taak van het domein om de service te creeren.

    Geplaatst op 07 januari 2011 om 7:46 Permalink

  • Andries Nieuwenhuize schreef:

    Nog een mooi voorbeeld: Singleton Applicaties.

    Probeer maar eens meerdere instanties van je Disk Defragmenter op te starten. Daarvan wil je er echt niet vier tegelijkertijd aan het werk hebben. Mij lukt het igg. niet, ondanks mijn dual core.

    Als reactie op alles wat je hierboven geschreven hebt: Singletons, daar kun je niet om heen! Hoe lastig het soms ook te realiseren is.

    Nu jij weer Matthijs…

    Geplaatst op 06 januari 2011 om 17:03 Permalink

    • Stefan schreef:

      Het is een eeuwige discussie tussen de voor- en tegenstanders van het Singleton patroon. Ikzelf heb er geen probleem mee, maar het is een kwestie van smaak. Het is daarnaast ook een discussie zonder eind haha…

      Geplaatst op 06 januari 2011 om 19:43 Permalink

  • Matthijs Holsbrink schreef:

    Nee, geen singleton keyword, en ik zou willen zelfs willen opteren dat Microsoft het STATIC keyword uit C# haalt. :)

    Geplaatst op 06 januari 2011 om 14:09 Permalink

  • Andries Nieuwenhuize schreef:

    Los van het feit of een Singleton zinvol is (wat m.i. zeker het geval is) staat het probleem van een Singleton garanderen / afdwingen in code.

    Als eerste zou ik zeggen dat dit de verantwoordelijkheid van de Singleton zelf is. Wat dacht je hier van? (http://dhtmlkitchen.com/learn/js/singleton/) Singleton in Javascript :-).

    Ten tweede zou ik opteren voor een singleton keyword want het is in een multi-threaded omgeving gewoon lastig te realiseren. De Singleton moet idd. ver buiten zijn scope en boven alle threads uit kunnen kijken om zijn uniekheid te garanderen.

    Gaan we Microsoft vragen om deze implementatie?

    Geplaatst op 06 januari 2011 om 13:59 Permalink

  • Matthijs Holsbrink schreef:

    En op welk niveau wordt de instantie van jouw singleton bijgehouden? VM (Java)? Classloader (Java)? AppDomain (C#)? Process? Thread?

    Wil jij state bijhouden in je singleton? Zo ja, hoe ga jij afdwingen dat jouw webserver niet jouw state om zeep helpt? In het geval van een webapplicatie lijkt het mij erg risicovol om een singleton te gebruiken, simpelweg omdat je meerdere worker processen hebt.

    Er is niks mis met het idee dat een object maar één keer mag bestaan. Maar wie beheert de lifecycle van dat object? Je wilt je object creatie dan neerleggen bij het framework. bij ASP.NET in een IHttpModule, en bij Java in een of andere IoC configuratie. Web frameworks in java hebben ook vast wel een goede plek om je objecten lifecycle goed te regelen.

    Geplaatst op 06 januari 2011 om 13:27 Permalink

  • Stefan schreef:

    En waarom dan niet? Op zich is er niks mis met het afdwingen dat er ten alle tijden maar 1 instantie mag bestaan. Bij een Facade is dat bijvoorbeeld wel handig. Wanneer je echt wilt dat er maar 1 instantie bestaat is een Singleton wel handig. Vooral wanneer iemand die je documentatie niet leest een 2e instantie aan gaat maken omdat ‘het zo makkelijker is.’ Ik geef toe, het Singleton patroon is in veel gevallen zinloos en op een betere manier op te lossen, maar niet in alle.

    Geplaatst op 06 januari 2011 om 12:54 Permalink

  • Matthijs Holsbrink schreef:

    Stefan, en als er dus over gaat nadenken kom je erachter dat je nooit het Singleton patroon moet gebruiken. :)

    Dus met het singleton pattern ansich is niks mis, maar met het gebruik wel ;)

    Geplaatst op 06 januari 2011 om 12:19 Permalink

  • Stefan schreef:

    Hoi Mathijs,

    Ik begrijp die NullReferenceException nog niet helemaal. Als je de GetInstance() aanroept wordt deze in die methode toch geïnitieerd? Eigenlijk zou dit hetzelfde moeten zijn als new OrderService().PlaceOrder(this, product);. Het lijkt wel alsof er iets mis gaat binnen de GetInstance(). Want als je de Singleton goed aanmaakt, wordt de constructor aangeroepen binnen die GetInstance() (of nog eerder d.m.v. private static final OrderService INSTANCE = new OrderService();) en is het niet mogelijk om deze apart te initiëren.

    Ik heb het net ook zelf even getest en bij mij werkt het gewoon. Zowel in de code als in de unit test. Bij mij wordt de OrderService dus wel geïnitieerd. Is dit een verschil tussen Java en C# of is er nog een onderdeel dat ik niet goed begrepen heb?

    De rest van je verhaal begrijp ik wel, hoewel de Singleton best handig kan zijn als je er maar goed over nadenkt.

    Groeten,

    Stefan

    Geplaatst op 06 januari 2011 om 11:23 Permalink

    • Matthijs Holsbrink schreef:

      Misschien moet jouw OrderService geinitialiseerd worden door OrderService.Setup() te doen. Dit is verborgen. Echter constructor Customer(OrderService orderService) stelt specifieke eisen aan de gebruiker van de klasse.

      Voor de gebruiker van mijn klasse is het precies duidelijk wat de afhankelijkheden zijn.

      Geplaatst op 06 januari 2011 om 11:28 Permalink

      • Stefan schreef:

        Ik hoefde niks te doen. Alles gebeurde automatisch. Het kan best zijn dat er iets niet wordt geïnitieerd, maar de Singleton wordt dat wel. Maar als de Singleton deze problemen geeft is het dus niet de juiste oplossing voor jouw probleem.

        Geplaatst op 06 januari 2011 om 11:32 Permalink

        • Matthijs Holsbrink schreef:

          Het is dan ook een hypothetisch voorbeeld. Het pattern zelf dwingt inderdaad af dat er altijd een instantie is. Maar het zou niet ondenkbaar zijn dat mensen een Singleton’s gaan gebruiken om applicatiebrede zaken te initialiseren middels een setup methode. Concreet: Op het project waar ik nu zit maakt men gebruik van een “BusinessFactory”. Daarin zit een “Initialize” methode, als ik die niet aanroep voordat ik “GetInstance” aanroep, krijg ik een “NotInitilializedException”.

          Geplaatst op 06 januari 2011 om 11:41 Permalink

          • Stefan schreef:

            Maar dan is het Singleton patroon zelf dus niet het probleem, maar leent de situatie zich er niet naar. De Singleton is een patroon dat je niet zomaar moet gebruiken, maar eerst goed over moet nadenken of het wel zinvol (en in dit geval te gebruiken) is.

            Geplaatst op 06 januari 2011 om 11:46 Permalink

          • Peter schreef:

            Dan wordt hier inderdaad ten onrechte het singleton pattern gebruikt. GetInstance() zou nooit mogen falen omdat er eerst een andere methode aangeroepen moet worden.
            (Of dit zou GetInstance() alsnog zelf moeten doen, waardoor deze weer zijn verantwoordelijkheid neemt.)

            Geplaatst op 06 januari 2011 om 11:45 Permalink

  • Peter schreef:

    De meeste punten die je noemt, kloppen wel. Wat ik echter wel vreemd vind, is dat je aangeeft dat de singleton bij het aanroepen van GetInstance() misschien nog niet is geïnitialiseerd. Volgens mij moet GetInstance() altijd zorgen dat er een geldig object wordt teruggegeven. Als er nog niets is, moet dit juist hier worden geïnitialiseerd. Dus deze situatie betekent volgens mij dat het singleton pattern niet helemaal juist wordt gebruikt (en zegt dus weinig over het pattern zelf).

    Wat ik zelf vaak tegenkom, is dat singleton wordt gebruikt voor helper/factory classes met simpele functies om datatypen te converteren of om instanties van een andere class aan te maken. In dit geval vind ik het zelf veel mooier om gewoon een static class te maken. Deze doet in feite hetzelfde als een singleton, maar is een klein beetje simpeler en dus mooier.
    Maar hier valt tegenin te brengen dat static classes met private static members conceptueel gezien lelijk zijn. Heeft iets instantie-variabelen, dan is het eigenlijk per definitie een object en niet iets vaags (lees: static). Maar een class die GEEN instantie-variabelen heeft, zou volgens mij nooit een singleton moeten zijn.

    Geplaatst op 06 januari 2011 om 11:10 Permalink

    • Matthijs Holsbrink schreef:

      Het zou kunnen dat OrderService een “Setup()” methode heeft om te initialiseren. Je kunt het inderdaad zo maken dat GetInstance altijd een geldige instantie teruggeeft. Maar dat wordt problematisch als je iets vanaf buitenaf moet meegeven zoals bijvoorbeeld: OrderService.Setup(url)

      Uiteindelijk heb je verschillende initialisatie methodes op verschillende plekken in je applicatie. Je kunt geen test bouwen, omdat het niet duidelijk is waar je zaken moet initialiseren.

      Geplaatst op 06 januari 2011 om 11:23 Permalink

  • Andries Nieuwenhuize schreef:

    De stok in het hoenderhok! Heerlijk!

    Als ik jouw betoog samenvat neigt het gebruik van Singletons naar global state variabelen die de programmeur verleidt tot het bouwen van applicaties met high coupling. Immers, je kunt een Singleton nu direct benaderen zonder de buren van je buur daarom te vragen omdat je zeker weet dat er toch maar één instantie bestaat en je dus altijd de juiste hebt.

    Dit lijkt me dan een verkeerd gebruik van een Singleton. Het pattern an sich is m.i. toch zeker wel zinvol. Het functioneert immers als een contraint die je nodig hebt om je domein te modelleren. Eén database vind ik niet zo’n goed voorbeeld. Dat je maar één printer hebt of dat een bedrijf maar één directeur kent kun je zo mooi afdwingen / modelleren. Hoe zou je dat anders willen doen?

    Ergo, goede stelling. Ik zou zeggen: Bedenk eerst wat je wilt wanneer je een Singleton gebruikt.
    Nee als het gaat om global access.
    Ja als het gaat om het modelleren van je domein. (contraints)

    Ben benieuwd naar andere reacties!

    Geplaatst op 06 januari 2011 om 11:03 Permalink

    • Matthijs Holsbrink schreef:

      Een bedrijf heeft maar 1 directeur? Eens, maar daar heb ik toch geen singleton voor nodig? Ik zou dat zo modelleren:

      class Bedrijf
      {
      private Persoon directeur;
      }

      Klaar toch?

      Geplaatst op 06 januari 2011 om 11:33 Permalink

      • Andries Nieuwenhuize schreef:

        Als het bedrijf de directeur creëert heb je gelijk. En in bovenstaande model heb je dat ook afgedwongen. Maar dit hangt helemaal af van je domein. Mijn punt is, ondanks het zwakke voorbeeld, dat afhankelijk van je model, je wilt afdwingen dat er maar één enkele instantie van iets bestaat.
        Nu kun je idd. vragen, bedrijf.GetDirecteur(). Maar als je het ‘bedrijf’ modelleert als een groep samenwerkende personen zonder en overkoepelende klasse ‘Bedrijf’ kun je dat dus niet. Dan is/kan een Singleton handig zijn. Misschien is een vereniging een betere term voor dit voorbeeld. Als het goed is heb je in je OO-week het spel Mancala ook zo gemodelleerd. Je vraagt aan de ‘bakjes’ zelf wie hun tegenoverliggende ‘bakje’ is en niet aan het spelbord. Je kunt je afvragen wat een Bedrijf / Vereniging nu wezenlijk is. Een gebouw? Muren? Is dat een klasse waard? Dus, dan is de Singleton toch wel handig denk ik.

        Geplaatst op 06 januari 2011 om 11:53 Permalink