Patternskolen del 2: Singleton vs. Static gateway

I del 2 av Patternskolen ser vi på Singleton og Static gateway.

Bjørn Herve Moslet

I del 1 av Patternskolen skrev jeg om forskjellen mellom Proxy, Adapter og Facade. Her kommer del 2 der jeg tar for meg Singleton og Static gateway.

Singleton

Singleton er ofte et av de første patterns man lærer seg, både fordi det er utbredt og fordi det er lett å lære. Kort fortalt er det når en klasse er begrenset til instansiering av kun ett objekt. Vi kan ikke bruke nøkkelordet «new» for å opprette flere instanser av den aktuelle klassen. Når programmet kjører vil det finnes kun én instans av klassen og all kode som bruker klassen må benytte denne ene instansen.

Den vanligste årsaken til å bruke Singleton er for å kontrollere tilgangen til en delt ressurs. Det klassiske eksempelet er en logger, men det kan også være en printer spooler, en connection pool, et factory-objekt eller tilsvarende.

Selv om konseptet er enkelt, kan implementasjonen ofte være utfordrende. Det hele avhenger av hvilken støtte programmeringsspråket gir. En potensiell feilsituasjon er race conditions hvis implementasjonen ikke er trådsikker. Et annet spørsmål er om instansen skal opprettes ved behov eller ved programmets oppstart. Dette er av betydning hvis instansieringen er tid- eller ressurskrevende. Eksempler på korrekt fremgangsmåte finnes både for Java og C#. Felles for implementasjonene er at singleton-klassen har privat konstruktør og en metode/property som gir tilgang til denne ene instansen.

Singleton – et anti-pattern?

Mange anser Singleton for å være et anti-pattern, bl.a. fordi det brukes for ofte, setter unødige restriksjoner og innfører globale tilstander i applikasjonene. Ofte kan det samme oppnås ved å bruke statiske metoder. Som påpekt, er det fort gjort å implementere det «feil» med de konsekvensene det kan gi.

En kan også hevde at Singleton bryter med gode prinsipper innen objektorientering. En singleton-klasse gjør to ting, både oppretter én instans av seg selv og selve kjernefunksjonaliteten (f.eks. logging). Dette bryter med Single Responsibility Principle. Det blir også en tett kobling mellom singleton-klassen og alle som bruker den. Konsumentene vil typisk ha en direkte avhengighet til denne instansen og det bryter med Inversion of Control.

Og, ikke minst, Singleton gjør enhetstesting veldig vanskelig. Uten Inversion of Control er det vanskelig å bytte ut singleton-klassen med et mock-objekt i testkoden. Singletons globale tilstand kan også medføre at tester påvirker hverandre, avhengig av hvilket testrammeverk man bruker og hvordan testene er skrevet.

Static gateway

Gateway patternet minner mye om Facade, som beskrevet i et tidligere innlegg. Implementasjonen er lik, men intensjonen er forskjellig. En sammenligning av Gateway og Facade kan leses på Stack Overflow.

Static gateway er en utvidelse av Gateway og forskjellen ligger i at metodene er statiske. Det anbefales å lese hele beskrivelsen, men under følger et eksempel i C# på implementasjon av lesing fra en konfigurasjonsfil.

Eksempel

public class Konfigurasjon
{
private static IKonfigurasjonFactory _konfigurasjonFactory = new KonfigurasjonFactory();

public static void OverstyrKonfigurasjonFactory(IKonfigurasjonFactory konfigurasjonFactory)
{
_konfigurasjonFactory = konfigurasjonFactory;
}

public string LesKonfigurasjon(string nøkkel)
{
return _konfigurasjonFactory.LagLeser().LesKonfigurasjon(nøkkel);
}
}

public interface IKonfigurasjonFactory
{
IKonfigurasjonsleser LagLeser();
}

public interface IKonfigurasjonsleser
{
string LesKonfigurasjon(string nøkkel);
}

public class Konfigurasjonsleser : IKonfigurasjonsleser
{
public string LesKonfigurasjon(string nøkkel)
{
return System.Configuration.ConfigurationManager.AppSettings[nøkkel];
}
}

Klassen Konfigurasjon er static gatewayen. Den har metoden OverstyrKonfigurasjonFactory for å bytte ut implementasjonen av IKonfigurasjonFactory. Denne settes typisk én gang ved oppstart av applikasjonen og i dette tilfellet gis den en standardverdi. Videre har klassen metoden LesKonfigurasjon for å lese konfigurasjonsverdier (men kunne hatt flere, for databasekoblinger osv.).

Klassen som implementerer interfacet IKonfigurasjonFactory sørger for å opprette konfigurasjonsleseren ved behov. Ved å bytte factory-implementasjon kan vi ta i bruk ulike konfigurasjonslesere uten at det påvirker resten av applikasjonen (f.eks. en som leser fra database i stedet for fra fil). Med fordel kan en IoC container brukes i stedet for en factory.

Implementasjonen av interfacet IKonfigurasjonsleser gjør lesingen av konfigurasjonen.

En forenkling ville vært å kutte ut Factory-klassen. IKonfigurasjonsleser settes da som parameter til en metode vi kunne kalt OverstyrKonfigurasjonsleser». En mister da kontrollen over ressursbruken ved opprettelse av kostbare objekter, men det er ikke alltid vi trenger det.

Fordeler med Static gateway

Det vil eksistere en tett kobling mellom (static) gatewayen og de klassene som konsumerer den. Fordelen med dette er at man slipper å «injecte» denne over alt. Øyensynlig strider dette mot alt vi har lært, men siden vi kan bytte ut objektet bak gatewayen er ikke det et problem.

Dette gjør at vi lett kan enhetsteste klassene som benytter gatewayen. I eksempelet må IKonfigurasjonFactory og IKonfigurasjonsleser byttes ut med mock-objekter.

En annen fordel er at store objekter ikke holdes i minnet lenger enn nødvendig. En singleton holdes i minnet til applikasjonen avsluttes. Det gjelder også for static gateway, men denne krever lite ressurser fordi den kun videreformidler metodekall til den faktiske implementasjonen. Den instansen kan vi kvitte oss med straks vi er ferdig med den, slik _konfigurasjonFactory.LagLeser() gjør i eksempelmetoden LesKonfigurasjon.

Konklusjon

Singleton er et pattern som det er lett å misbruke. Det kan være vanskelig å implementere riktig og kommer sjelden til sin rett. Har du likevel bruk for denne funksjonaliteten, bør du vurdere Static gateway i stedet.

Her finner du del 3 i Patternskolen. Det handler om Observer vs. Mediator.

Temaer