3
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)
public static class GlobalVariables
{
public static string FileName = @"c:\fil1.txt";
public static int Add(int a, int b)
{
return a + b;
}
}
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.
public class Registry
{
private static Registry instance;
/// <summary>
/// Skjuler vores public constructor
/// </summary>
private Registry()
{
}
/// <summary>
/// Bliver kun kaldt een gang, og det er første
/// gang den refereres.
/// </summary>
static Registry()
{
instance = new Registry();
}
/// <summary>
/// Public adgang til selve den instans
/// der er lavet i vores statiske constructor
/// dette er den vigtigste property i klassen
/// </summary>
public static Registry Instance
{
get { return instance; }
}
/// <summary>
/// Simpel metoder der adderer
/// </summary>
public int Add(int a, int b)
{
return a + b;
}
/// <summary>
/// Simpel metoder der returnerer en værdi
/// </summary>
public int HentMoms()
{
return 25;
}
}
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:
int sum = Registry.Instance.Add(5, 6);
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.
/// <summary>
/// Simpel metoder der returnerer en værdi
/// </summary>
public virtual int HentMoms()
{
return 25;
}
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.
/// <summary>
/// Simpel metoder der sætter/skifter instans
/// </summary>
public static void Initialize(Registry newRegistry)
{
instance = newRegistry;
}
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:
/// <summary>
/// Simpel metoder der returnerer en værdi
/// </summary>
public override int HentMoms()
{
return 42;
}
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:
int moms = Registry.Instance.HentMoms();
Console.WriteLine(moms) //skriver 25
Registry.Initialize(new NewRegistry());
moms = Registry.Instance.HentMoms();
Console.WriteLine(moms); //skriver 42
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.
public class Registry
{
private static Registry instance;
/// <summary>
/// Skjuler vores public constructor
/// </summary>
private Registry()
{
}
/// <summary>
/// Bliver kun kaldt een gang, og det er første
/// gang den refereres.
/// </summary>
static Registry()
{
instance = new Registry();
}
/// <summary>
/// Simpel metoder der sætter/skifter instans
/// </summary>
public static void Initialize(Registry newRegistry)
{
instance = newRegistry;
}
/// <summary>
/// Public adgang til selve den instans
/// der er lavet i vores statiske constructor
/// dette er den vigtigste property i klassen
/// </summary>
public static Registry Instance
{
get { return instance; }
}
/// <summary>
/// Simpel metoder der adderer
/// </summary>
public int Add(int a, int b)
{
return a + b;
}
/// <summary>
/// Simpel metoder der returnerer en værdi
/// </summary>
public virtual int HentMoms()
{
return 25;
}
}
public class NewRegistry : Registry
{
public override int HentMoms()
{
return 42;
}
}
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)
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?
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.
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:
private static Registry instance = new Registry();
Min foretrukne måde at lave singletons på er følgende:
- public sealed class NHibernateSessionManager
- {
- ...
-
- private NHibernateSessionManager()
- {
- InitSessionFactory();
- }
-
- public static NHibernateSessionManager Instance
- {
- get
- {
- return Nested.NHibernateSessionManager;
- }
- }
-
- private class Nested
- {
- static Nested() { }
- internal static readonly NHibernateSessionManager NHibernateSessionManager =
- new NHibernateSessionManager();
- }
-
- ...
- }
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.
@Nikolaj Dam Larsen, man kunne også bruge Lazy<T>!
- public class Singleton
- {
- private Singleton()
- {
-
- }
-
- private static readonly Lazy<Singleton> _lazy = new Lazy<Singleton>(() => new Singleton());
- public static Singleton Instance
- {
- get
- {
- return _lazy.Value;
- }
- }
- }
@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:
- /// <summary>
- /// Generic lazy-loaded threadsafe Singleton
- /// </summary>
- /// <example>
- /// public class Demo
- /// {
- /// public static Form1 instance1
- /// {
- /// get
- /// {
- /// return Singleton<Form1>.Instance;
- /// }
- /// }
- /// }
- /// </example>
- /// <typeparam name="T">Any class that implements default constructor</typeparam>
- public sealed class Singleton<T> where T : new()
- {
- private Singleton()
- {
- }
- /// <summary>
- /// Get an lazily loaded thread-safe instance of the generic type parameter
- /// </summary>
- public static T Instance
- {
- get { return Nested.instance; }
- }
- private class Nested
- {
- // Explicit static constructor to tell C# compiler
- // not to mark type as beforefieldinit
- static Nested()
- {
- }
- internal static readonly T instance = new T();
- }
- }
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. :-)
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.