Categorie
Programmazione

Singleton – Design Pattern Creazionale

Lo scopo del pattern Singleton è quello di assicurare che una classe ha una sola istanza e provvedere un unico punto di accesso ad essa.

E’ un pattern molto semplice da implementare, ma e’ bene comprendere che non va abusato! Una cosa che cerco di spiegare sempre e’ che un refactoring del codice non introduce benefici solo perche’ e’ stato utilizzato un pattern.

Il Singleton va usato solo quando si verificano queste condizioni:

  • devi assicurare che esiste una sola istanza della classe
  • e’ essenziale l’accesso controllato all’istanza della classe
  • potresti aver bisogno di piu’ di un’istanza solo in stage successivi (agile? come uno stambecco 😉 )
  • il controllo deve essere localizzato nell’istanza della classe, non in altri meccanismi esterni

Applicare il pattern Singleton ad una classe significa che:

  • la classe non potra’ essere istanziata con ‘new’ (costruttore privato?)
  • aggiungere un campo privato, statico e final che viene istanziato utilizzando il costruttore privato
  • esiste un metodo statico pubblico getter per l’oggetto privato.

Chiaro, no?

UML Classe Singleton
Diagramma UML di una classe Singleton

Al momento del design della classe Singleton bisogna decidere che tipo di inizializzazione dare: lazy o immediata.

Il lazy-loading e’ un altro design pattern usato spesso per rimandare l’inizializzazione di un oggetto fino al punto in cui e’ necessario. Puo’ contribuire all’efficienza dell’applicazione, specie se non e’ sicuro che l’oggetto verra’ usato. L’inizializzazione immediata, eager-loading per gli anglosassoni, e’ l’opposto: inizializza tutto all’avvio dell’applicazione.

La decisione su quale inizializzazione usare, va presa soppesando vari fattori: c’e’ possibilita’ che l’oggetto venga mai utilizzato? L’inizializzazione potrebbe richiedere troppo tempo? ho tutti i dati necessari per l’inizializzazione immediata?

Nel caso di inizializzazione immediata basta istanziare immediatamente l’oggetto privato:

class FooSingleton {
    private static final fooSingleton = new FooSingleton();

    public static FooSingleton getIstance() {
        return fooSingleton;
    }
}

mentre in caso di lazy-loading o “Initialization on Demand Holder”:

class FooSingleton {
    private static final fooSingleton ;

    public static FooSingleton getIstance() {
        if (fooSingleton == null) {
            fooSingleton = new FooSingleton();
        }
        return fooSingleton;
    }
}

L’oggetto viene istanziato nel metodo solo se e’ null, quindi solo alla prima chiamata.

Il metodo getIstance() deve assicurare che solo un’istanza della classe singleton esista.

Il codice scritto sopra non funzionera’ correttamente in caso di ambiente multithreaded (perche’?).

Il libro “Concurrency Programming in JAVA” consiglia la seguente implementazione usando un lock:

class FooSingleton {
    private static final FooSingleton fooSingleton;
    private static final Object classLock = FooSingleton.class;

    public static FooSingleton getIstance() {
        synchronized(classLock)
        {
            if (fooSingleton == null) {
                fooSingleton = new FooSingleton();
            }
            return fooSingleton;
        }
    }
}

Il metodo GetIstance() puo’ essere migliorato affinche’ solo la prima volta si entri nel blocco synchronized. Questo ha un impatto positivo sulle performance, in quanto il blocco synchronized è utile solo nella fase iniziale in cui l’istanza non è ancora stata creata, quando è indispensabile la sincronizzazione, e di conseguenza è un inutile spreco di tempo per tutto il resto del ciclo di vita del singleton in cui viene semplicemente restituita l’istanza già creata.

Una possibile soluzione puo’ essere questa (detta double-check idiom):

class FooSingleton {
    private static volatile FooSingleton fooSingleton;
    private static final Object classLock = FooSingleton.class;

    public static FooSingleton getIstance() {
        if (fooSingleton == null) {
            synchronized (classLock) {
                if (fooSingleton == null) {
                    fooSingleton = new FooSingleton();
                }
            }
        }
        return fooSingleton;
    }
}

La soluzione piu’ elegante, che solo JAVA permette, comporta lo sfruttare alcune specifiche della JVM: JVM Initialization of Classes and Interfaces.

Questa implementazione e’ stata ideata da Bill Pugh ed e’ la migliore al momento (anche per eleganza).

class Singleton {

  private Singleton() {}

  private static class SingletonHolder  {
    private final static Singleton singletonIstance = new Singleton();
  }

  public static Singleton getInstance() {
    return SingletonHolder.singletonIstance;
  }
}

Alcuni link utili:

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *