© 1999-2003, Flemming Koch Jensen
Alle rettigheder forbeholdt
Singleton Pattern

Opgaver

"Spouse, n.: Someone who'll stand by you through all the trouble you wouldn't have had if you'd stayed single"

- Ukendt ordbog

 

Forudsætninger:

At man har læst kapitlet Klasser som objekter, eller på anden måde har opnået et tilstrækkeligt kendskab til static. Det er en forudsætning for at forstå afsnittet om implementation med anonyme klasser, at man har kendskab til indre klasser - evt. ved at have læst kapitlet Indre klasser.

 

 

Ønsker/Interesser
Kun at tillade netop én instans af en given klasse.
At der er nem adgang til en sådan instans.

 

Eksempel

Hvis man lader et objekt repræsentere et debug-vindue, og man ønsker at dette objekt samtidig skal styre output til vinduet, er det naturligvis hensigtsmæssigt, at der kun findes ét sådant objekt - og dermed kun ét debug-vindue. Hvis alle klienter, som har behov for at sende debug-beskeder til programmøren laver deres eget vindue skal han aflæse forskellige vinduer for at skabe sig et overblik over programudførelsen. Vi ønsker derfor ikke at klienterne selv skal instantiere debug-vinduet.
Ikke alene vil det være en fordel, at der kun findes ét sådant vindue, men det vil ligeledes være en fordel, at det er globalt tilgængeligt - at enhver klient har nem adgang til det.

 

Løsning

Løsningen baserer sig på klasse-variable og -metoder. Lad os kalde den klasse vi kun vil tillade én instans af for Singleton. Hvis vi for eksemplets skyld antager, at denne klasse kun har en enkelt instans-metode råb, der udskriver en tekst med ene store bogstaver, vil klassen få følgende implementation:
class Singleton {
  
  /*
   * Klasse-variable og -metoder
   */
  
  private static Singleton inst=null;
  
  public static Singleton instance() {
    if ( inst == null )
      inst = new Singleton();
      
    return inst;
  }
  
  /*
   * Instans-variable og -metoder
   */
  
  private Singleton() {
  }
  
  public void råb( String s ) {
    System.out.println( s.toUpperCase() );
  }
}
class Main {
  
  public static void main( String[] argv ) {
    
    Singleton vorSingleton = Singleton.instance();
    
    vorSingleton.råb( "Hallo, er der nogen?" );
  }
}
HALLO, ER DER NOGEN?
Der er to centrale elementer i løsningen: Konstruktoren og instance-metoden.

 

Konstruktoren

Konstruktoren synes ved første øjekast at være ligegyldig, endog overflødig. Vi har her anvendt en default-konstruktor, da eksemplet er meget enkelt. Hvis det eneste man har i en klasse, er en tom default-konstruktor, behøver man normalt ikke lave én - det gør Java selv. Der er dog en speciel ting ved vores konstruktor, der gør at vi ikke kan bruge Java's default-konstruktor - vores er private!
Når noget er private kan det, som bekendt, ikke tilgås udenfor klassen. Vores private konstruktor kan kun tilgås indefra klassen, og dermed er det kun klassen der kan lave nye instanser af Singleton, da instantiering fordrer at man kan tilgå den konstruktor man anvender.
At gøre konstruktoren private er vores måde at forhindre andre i at lave instanser af Singleton, men hvordan foregår instantieringen så?

 

instance-metoden

Det er her instance-metoden kommer ind i billedet. Det er instance-metoden der laver instancer af klassen - nærmere bestemt én.
Metoden bruger en klasse-variabel til, både at huske, og holde fast i den ene instans den laver. instance-metoden består i hovedsagen af en if-sætning, der checker om vi allerede har lavet en instans ved at sammenligne inst med null.

 

Interaktionsdiagram

Interaktionen i eksemplet kan illustres med følgende sekvensdiagram:
Figur 1:
Sekvens-diagram

 

Lazy instantiering

Lille plus Sådan som vi har valgt at implementere instance-metoden, giver Singleton Pattern os et ekstra lille plus!
Hvornår? Hvornår instantieres Singleton'en? Det gør den første gang instance-metoden kaldes. Hvornår kaldes instance-metoden? Det gør den i en af to situationer:

Vi ønsker en reference til Singleton'en, til senere brug. Det kunne f.eks. være hvis vi ønskede at have en reference i vores datakerne, så vores metoder ikke behøvede at hente den; hver gang vi skulle bruge den.

Vi ønsker en reference til Singleton'en forbi vi skal bruge den nu.

Hvis vi holder os til sidstnævnte anvendelse, vil Singleton'en ikke blive instantieret før vi skal bruge den. Vi har i princippet hele tiden adgang til den, men det er først når vi reelt bruger den - ved at kalde instance-metoden - at instantieringen sker.
Doven At instantieringen ikke sker før det er strengt nødvendigt, kaldes lazy instantiering (dk.: doven instantiering). Fordelen ved lazy instantiering er at vi ikke skal "betale" de omkostninger, der er forbundet med at instansen eksisterer før strengt nødvendigt. Sådanne omkostninger kunne være diverse resourcer - så som lagerplads, netværksforbindelser osv.
Sjældent Om lazy instantiering er en gevinst, beror på den konkrete situation - er der nogen resourcer som man sparer på? Det er derfor lazy instantiering ovenfor generelt betegnes som et lille plus. Det er relativ sjældent at det betyder noget.

 

Hvem "sletter" en Singleton?

Normalt vil garbage collectoren fjerne objekter, som ingen længere refererer til. Det vil den naturligvis også for en Singleton's vedkommende, men der er et problem. Singleton klassen opretholder selv en reference til instancen. Derfor vil den reference-løse situation aldrig opstå!
Man kunne evt. forestille sig at Singleton-klassen så kunne sætte sin reference til null, og på den måde give Singleton'en fri, så den kunne slettes. Men hvad så hvis andre stadig har en reference til den?

Hvis en eller flere klienter fortsat har en reference til instansen og andre klienter kalder instance-metoden, går det galt! instance-metoden vil tro, at der ikke findes nogen instans af Singleton - déns reference er jo null, og den vil lave en ny. Resultatet er nu to instanser af Singleton, og vores væsentligste designmål er forfejlet.

Jeg er ikke bekendt med nogen løsning på dette problem, men det er et af ynglingsemnerne når man diskuterer Singleton Pattern.

 

Varianter

Der findes ikke egentlige varianter af Singleton Pattern. Man kan dog variere om antallet af instanser skal være netop én, i stedet for flere. Hvis en Singleton f.eks. ønsker maksimalt tre instanser, kan den opretholde et statisk array med tre referencer, og blive ved med at lave nye instanser så længe nogen af indgangene stadig er null. Man kunne evt. forestille sig dette brugt i forbindelse med en objekt-pool.

 

Implementation med anonyme klasser

Med en anonym klasse kan man sikre at instantiering kun foregår ét sted.
Vi kunne f.eks. lave en instans med:
inst = new Singleton() {
  public void råb( String s ) {
    System.out.println( s.toUpperCase() );
  }
};
Dette forhindrer naturligvis ikke, at der laves flere instanser af en anonym klasse, da den sætning der foretager instantieringen kan udføres flere gange - f.eks. med en løkke, eller ved at den optræder i en fabriks-metode. At instantieringen kun kan ske ét sted, gør det dog nemmere at sikre, at den også kun sker én gang.
Hvis vi kombinerer løsningen med en statisk metode til at styre adgangen til vores singleton, og anvender en anonym klasse i forbindelse med instantieringen, får vi:
abstract class Singleton {
  
  /*
   * Klasse-variable og -metoder
   */
  
  private static Singleton inst=null;
  
  public static Singleton instance() {
    if ( inst == null )
      inst = new Singleton() {
        public void råb( String s ) {
          System.out.println( s.toUpperCase() );
        }
      };
      
    return inst;
  }
  
  /*
   * Instans-metoder
   */
  
  public abstract void råb( String s );
}
Det er en løsning, der i opbygning med statiske dele, ligner den løsning vi tidligere har set. Der er dog visse forskelle?
Først og fremmest har vi ikke længere en privat konstruktor - vi har i det hele taget ikke nogen konstruktor. Singleton-klassen kan derfor gøres abstrakt, da den nu kun optræder som superklasse til den anonyme klasse vi instantierer. Før sikrede den private konstruktor, at klienter ikke kunne lave deres egne instanser af klasse, nu er det den abstrakte klasse og information hiding (den anonyme klasse kan ikke ses udenfor klassen) der forhindrer det.
Som man kan se, gør en løsning med indre klasser ikke det store fra eller til, så det valg man træffer må bero på mindre nuance-forskelle. Personlig foretrækker jeg løsningen uden anonyme klasser, da den anonyme klasse hurtigt gør kildeteksten uoverskuelig, og at en løsning med indre klasser ikke gør det muligt at nedarve fra én singleton til en anden.

 

Relationer til andre patterns

I relation til andre patterns er det primært forskellige former for fabriks-mønstre; hvor man ønsker at begrænse antallet af fabrikker til én (se evt. Abstract Factory Pattern og Builder Pattern (disse er ikke skrevet endnu))

 

Referencer

  [GoF94] s.127-134.
  [Grand98] s.127-133.
  [Buschmann96] s.350-352. (Her præsenteres Singleton Pattern som eksempel på idiom)