Singletons i .NET

Tags:    .net
Skrevet af Bruger #2730 @ 17.06.2008
Denne artikel handler om hvad et singleton pattern er, hvornår man kan bruge det samt hvordan man kan implementere det.

Om Patterns


Patterns, eller mønstre, er en standard måde at gøre nogle rutiner på. Hvis man skal drage en parallel til noget uden for programmeringsverden så kan man sige at patterns er matcher rimeligt godt på et mønster man syr tøj efter. Man har et mønster man kan sy et par bukser efter og med tiden videreudvikler man det mønster så det til sidst er så godt et mønster at man, hver gang man skal sy et par bukser, straks tager fat i det mønster, da det har hjulpet mange gange tidligere. Det betyder så også at det pattern kun virker til bukser, ikke til kjoler eller skjorter. Man kan forestille sig at man videreudvikler sit mønster til at kunne sy flere størrelser af bukser, både til børn og voksne, det vil sige at mønstret bliver mere generelt. Det samme er gældende inden for softwareudvikling, her kaldes det bare patterns. Et pattern er en standard måde at lave en software struktur på, således man slipper for selv at opfinde en måde at gøre det på hver gang. Der er mange patterns inden for mange områder, de mest kendte patterns indenfor softwareudvikling stammer fra fire amerikanere (Erich Gamma, Richard Helm, Ralph Johnson og John Vlissides) de patterns de har samlet bliver ofte refereret til som "GoF Patterns" (Gang of Four Patterns). Hovedsagelig er de delt op i tre kategorier, der henholdsvis står for at oprette nye objekter, binde objekter sammen i en struktur samt at skabe en vis opførsel af disse objekter (Creational patterns, structural patterns, behavioral patterns). Alt dette kan man læse videre om på Wikipedia ved at søge efter "Design Patterns".

Om Singleton


Et singleton pattern er et creational pattern, det vil sige at det er kategoriseret under de patterns der primært beskæftiger sig med at lave instanser af objekter. Hovedideen ved et singleton er at skabe kun eet objekt af den type, således det altid er det samme man får fat i, ligegyldigt hvad. Det virker måske lidt mærkeligt ved første øjekast, men det er faktisk ret praktisk. Lad os bruge et eksempel med en klasse, hvis formål er at holde nogle informationer som skal være tilgængelige alle steder i programmet (en slags globale variable, samt globalt tilgængelige metoder). Man taler generelt om at der specielt er tre ting der skal være opfyldte før man laver en Singleton kan retfærdiggøres: 1. Der er ingen direkte ejerskab af klassen, 2. Man vil gerne have så sen en initialisering som muligt (lazy initialization) og til sidst, 3. Global tilgang er ellers ikke muligt. Det man helst vil undgå i en sådan situation er at lave en klasse man skal lave en instans af hver gang og så bede den om at loade sine data fra en fil eller en anden datakilde. Hvis man vælger at gøre dette konflikter det lidt med den sene initialisering, for hvornår skal man så æave instansen, og kan det tænkes at der er andre objekter der kunne tænkes at ville tilgå instansen inden da (dette ville resultere i en null pointer exception). Det vi også vil undgå er at lave en statisk klasse med ene statiske variable. Dette ville godtnok løse vores problemer, men pænt er det ikke og vi kommer også lidt til kort hvis der er anden logik der skal instantieres på vores globale objekt - dette hænger sammen med den sene initialisering. Og hvad sker der hvis to objekter tilgår og retter i den samme globale variabel samtidig? Løsningen er ikke specielt trådsikker - men det er nødvendigvis heller ikke et mål i sig selv.

Koden


Der flere forskellige måder at lave en Singleton på, for at afslutte den ovenstående diskution med at vise den globale statiske klasse samt at vise hvordan man ikke bør lave sig en Singleton (hvis man overhovedet kan kalde den det)

Fold kodeboks ind/udKode 

Som tidligere beskrevet er ovenstående kode et eksempel på hvordan man ikke bør strukturere sin kode (det er der sikkert mange holdninger til). De to hovedårsager til at netop denne implementering ikke er stærk nok, er at den for det første ikke er trådsikker og for det andet ikke giver mulighed for at objektet kan holde instansvariable og initialisere disse. Den mest almindelige måde at lave en Singleton på er ved at lave en privat instansvariabel der holder en reference til objektet, skjule den public constructor og lave en statisk constructor der initialiserer instansvariablen og andre instansvariabler der måtte være i denne klasse. For at kunne skjule en public constructor er vi nødt til at lave en privat constructor således at denne overstyrer den public constructor, når dette er gjort er det ikke længere muligt at lave en instans af klassen. Det vil sige at man med en privat constructor på klassen MyClass ikke er muligt at gøre dette: MyClass myClass = new MyClass(); Dette vil give en compilerfejl. Det næste trin er en statisk constructor, den statiske constructor er også lidt speciel den bliver instantieret første gang klassen refereres og KUN der og KUN første gang. Dette betyder at vi nu har opfyldt kravet med at vi kan lave en sen initialisering, første gang der er behov for noget fra vores Singleton klasse bliver den initialiseret, og ikke før. Der er nu ikke længere nogen der skal bekymre sig om at objektet nu er initialiseret og om data nu er loaded. Nedenstående er et eksempel på hvordan man kan lave en simpel Singleton klasse.
Fold kodeboks ind/udKode 

Det førset der sker er som tidligere beskrevet at den private instansvariabel "instance" initialiserer i den statiske constructor (dette sker kun første gang der refereres). Der hvor det bliver interessant er i den public og statiske property "Instance", det er den der returnerer vores instans som vi oprettede i den statiske constructor. Når man så efterfølgende skal bruge vores Singleton klasse kan det gøres på følgende måde:
Fold kodeboks ind/udKode 

Simpelt og effektivt. Er det første gang i væres program at dette kaldes, så instantierer kaldet til "Instance" vores Singleton og den bliver ikke ændret efterfølgende, så alle efterfølgende kald vil få nøjagtigt den samme instans af vores Registry objekt. Men nu handler det at skrive software jo lige så meget om at kunne teste det man skriver. For at kunne teste denne "Registry" vi har lavet (og måske nemmere at kunne teste de metoder der gør brug af den) kan vi vælge at lave en lille variation af vores Singleton inspireret af Martin Fowlers "registry" pattern. Det vil vil gøre er at ændre vores klasse (den klasse vi har lavet singleton på) således at de metoder og properties der skal bruges er virtuelle, det vil sige at vi kan nedarve dem.

Fold kodeboks ind/udKode 


Det næste vi gør er at lave en metode på vores Singleton der gør os i stand til at skifte instansen ud med en anden. Det vi reelt gør er at lave en ny klasse der nedarver fra vores "Registry" klasse som vi har laver her ovenfor, lad os kalde den for "NewRegistry" den overstyrer nu alle de virtuelle metoder og laver kendte returværdier for de metoder, det giver os mulighed for specielt at teste de klasser og funktioner der implementerer vores registry pattern. Den metode vi tilføjer til vores "Registry" klasse er en metode der sætter en anden instans i vores Singleton.

Fold kodeboks ind/udKode 

Herefter har vi kun tilbage at ændre vores virtuelle metode "HentMoms()" således vi har en værdi vi kender og kan styre 100% fordelen ved det er at man ofte løber ind i nogle problemer fordi de properties der er på en Singleton klasse kan være sat af mange forskellige klasser igennem hele livscyklussen af programme, og ofte er de ikke så simple som i dette eksempel. Vi overstyrer nu vores virtuelle metode til at returnerer en kendt værdi:

Fold kodeboks ind/udKode 


Det vil sige at når der nu skal testes i koden kan vi hurtigt lave en instans af vores nye Singleton og bruge den til at teste med, vist nedenunder er et eksempel på hvordan det bruges:

Fold kodeboks ind/udKode 

Hele koden til begge klasser kan ses her nedenunder. Som afslutning vil jeg anbefale at man overveje at bruge det hvor det giver mening i stedet for at lave andre krumspring, samt at overholde de regler der er beskrivet i starten af denne artikel, de plejer som oftest at være gode holdepunkter.

Fold kodeboks ind/udKode 


Hvad synes du om denne artikel? Giv din mening til kende ved at stemme via pilene til venstre og/eller lægge en kommentar herunder.

Del også gerne artiklen med dine Facebook venner:  

Kommentarer (7)

User
Bruger #6559 @ 17.06.08 15:36
God artikel. Så fik jeg et andet eksempel på hvordan man laver en singleton. Men hvad er fordelen ved den måde du bruger i forhold til at man har en klasse hvor man kalder en funktion der opretter et object til instancen, hvis instancen er null?
User
Bruger #2730 @ 18.06.08 08:22
Med min begrænsede viden om .NET (og et hurtigt opslag på MSDN for at være helt sikker) ville jeg vove den påstand at den metode du beskriver ikke er trådsikker, mens det eksempel jeg har lavet er trådsikkert, da det er en static constructor, og den kun bliver kaldt hvis klassen instantieres eller et statisk element på klassen kaldes.
User
Bruger #11036 @ 19.08.08 16:24
Hej Brian.

To kommentarer:
1. Default constructor på Registry bør være protected i stedet for private. Brokker compileren sig ikke over protection level for NewRegistry?

2. Static constructors giver en performance penalty ift. at initialisere static fields inline, da C# kompileren vil benytte beforefieldinit flaget for klasser med kun inline initalisering:
Fold kodeboks ind/udKode 
User
Bruger #11164 @ 15.07.11 20:37
Min foretrukne måde at lave singletons på er følgende:

Fold kodeboks ind/udCSharp kode 


Den er både thread-safe og lazy, således at objektet først bliver oprettet i det øjeblik det bliver refereret første gang.
User
Bruger #14652 @ 21.08.11 00:09
@Nikolaj Dam Larsen, man kunne også bruge Lazy<T>!

Fold kodeboks ind/udCSharp kode 
User
Bruger #11164 @ 21.09.11 21:07
@Jens, godt set. Det har du helt ret i.

Har dog aldrig selv benyttet den. I det webframework jeg selv bruger og udvikler, har jeg en generisk singleton klasse. Det kunne ikke være ret meget nemmere.

Her er koden:
Fold kodeboks ind/udCSharp kode 

Værsgo' og tag hvad der kan bruges. Den må også gerne opdateres til at bruge Lazy<T> hvis det har interesse.

Så fik jeg vidst også rodet godt op i en gammel artikel. :-)
User
Bruger #2730 @ 25.09.11 22:12
Super kode, kan godt lide den... den var desværre ikke tilgængelig dengang jeg skrev min singleton :-) Kan også godt lide ideen med en Register(T t) metode, således man kan erstatte sin instans runtime med en stub fra sin unit test.
Du skal være logget ind for at skrive en kommentar.
t