Singleton design pattern i C# 1

Singleton er et “creational pattern” som har til formål at sørger for at der kun kan skabes et objekt af klassen. Singleton er et ret kendt design pattern fra “Gang of Four” design patterns. Det er et design pattern som er relativt simpelt men kan være et helvede hvis man gør det forkert, eller bruger det i et forkert sammenhængende.

Singleton er en metode man bruger når man skal være sikker på:

  1. Der må kun være en og kun 1 instance af denne klasse
  2. Man skal kunne have adgang til klassen
  3. Klassen ikke skal bruge paramenter i sin constructor.
  4. Hvis det er “dyrt” i ressourcer at skabe en instance af en given klasse, så kan singleton forbedre din performance.

Jeg har endnu ikke set eller hørt om et godt eksempel hvor singleton kan effektivt blive brugt. Et som går igen og igen, er logging til en fil hvor det kan være effektivt at benytte sig af singleton design pattern. Dog vil jeg i mit eksempel benytte mig noget mere simpelt.

Et simpelt singleton eksempel

Singleton er som jeg nævnte tidligere, en ret simpel klasse. Jeg vil i dette eksempel gå igennem en klasse, som kan genere et unikt nummer (blot for at holde det rigtig simpelt). For at undgå at man ved hjælp af new kan skabe et nyt objekt af sin singleton klasse, så giver man den ofte en private constructor. På denne måde er det kun klassen selv som kan instantiere et nyt objekt. Fora t lave vores helt unikke nøgle generator, så må vi sørger for at der kun er en instans af denne klasse:

Nu er det kun muligt at skabe en instans af vores KeyGeneartor klasse, og du har ikke mulighed for at skrive new KeyGenerator(); da vores constructor er private.

Eneste måde vi kan få adgang til klassen er ved at skrive:

Her går vi sådan set ind i vores KeyGenerator  statiske getter kaldet Instance, hvis klassens private instance er null, så opretter vi et nyt KeyGenerator objekt på instance.

Men vi er ikke helt færdige endnu, vores Singleton kan endnu ikke give os et unikt ID.

Derfor skal der tilføjes en method og en privat int:

Nu kan vi i en konsol app skrive følgende og få et unikt nummer:

og resultatet vil være: 1,2,3,4,5. Når du genstarter applikationen, så vil den altså starte forfra da applikationen ikke gemmer tallet den er nået til. Som sagt, et meget simpelt eksempel for at fremvise Singleton princippet.

Det slutter dog ikke der!

Det eksempel af singleton som du lige har set, er ikke “thread-safe”. Det betyder at 2 threads vil have mulighed for at evaluere at if  (instance == null) er true og derved skabe 2 instanser af KeyGenerator. Det vil bryde singleton design princippet.

Thread-Safe Singleton

En Thread-safe singleton version vil se således ud (code snippets):

Så hvad er det lige der sker her? Denne implementering af Singleton er thread-safe. En thread tager en lås på et delt object, og derefter tjekker om en instans af KeyGenereator er blevet oprettet før den opretter objektet.

Ved hjælp af lock, løses muligheden for at to threads kan oprette en instans af KeyGenerator, da locking gør at kun en thread kan være en del af den kode.  Thread nr. 2 vil derfor vente og kan først komme til at køre den del af koden, når thread nr. 1 har kørt rutinen og derfor oprettet KeyGenerator objektet.

Dette skaber selvfølgelig et performance hit, pga den lock, men kan være en nødvendighed.

Dobbelt-check thread locking singleton

Dobbelt-check thread locking vil se således ud:

I et dobbelt tjek, finder den ud af om instance == null, før den begynder at lave en lock, som sidst skaber en instans af KeyGenerator. Thread nr. 2, vil lave et tjek på instance == null, som vil ramme false og derfor spring lock delen over.

Denne løsning er et forsøg på at skabe en bedre performance ved at tage den del af koden ud hvor der indgår en lock. Denne løsning har dog sine ulemper, et af dem er af dette ikke virker i Java, dernæst så bryder det ECMA CLI specifikationen, den er rigtig nem at brænde nallerne på og den er stadig heller ikke super performance venlig.

Thread-safe uden lock

Det er også muligt at lave en singleton uden at benytte sig af lock, som er thread safe. Det er en rigtig simpel løsning som har en bedre performance end en singleton der benytter sig af en lock.

Thread-safe uden lock og lazy instantiation

I dette eksempel har vi både lazy instantiation af din singleton klasse, det vil sige den først bliver oprettet når der er behov for den. Dernæst så benytter vi ikke en lock, men vi er stadig thread-safe. Instantiation sker når vi trigger Instance metoden, som går ind i vores “Nested” klasse. Vores Nested klasse har adgang til alle private fields i “SingletonTest” men SingletonTest har ikke adgang den anden vej rundt. Derfor bruger vi internal keyword på instance i nested klasse.

Denne løsning er mere kompliceret men den har alle performance fordele og den er lazy instantiation..

Konklusion

Jeg har endnu ikke oplevet at jeg, i den virkelig verden, har brugt Singleton design pattern. Jeg har googlet en masse under min research og har endnu ikke fået et “Real world example” om hvornår det er virkelig smart at benytte sig af et singleton design pattern. Så vidt jeg kan læse mig frem til, så er det ikke en af de mest ønskede og brugte design patterns.

Her er et par ulemper der bliver nævnt på nettet ret ofte:

  • Du kan ikke bruge interfaces eller abstractions
  • Du kan ikke bruge subclasses / nedarve m.m
  • Highly coupled igennem hele din applikation
  • Rigtig svær at teste (Ingen unit test, da du ikke rigtigt kan mock/fake)

 

One comment on “Singleton design pattern i C#

  1. Pingback: Mit design pattern bibliotek i C# med notater og eksempler

Skriv et svar