Singleton: Pattern o Anti-Pattern?

S

INTRODUZIONE

Ciao Coders,

anche oggi vi parliamo di pattern creazionali dopo l’ottimo riscontro che avete dato su Builder Pattern e Factory Method Pattern.

In quasi tutti i progetti ci scontriamo sulla necessità di dover avere una singola istanza di una data classe condivisa da diverse classi.
Alcuni esempi sono uno spooler di stampa il quale deve tenere una coda unica, il manager di alcune risorse, una cache , il logger ecc. Il singleton pattern è una possibile soluzione a questa problematica.

definizione

Come di consueto partiremo con la definizione formale che il GoF ha dato per questo pattern

Il singleton Pattern ha lo scopo di assicurarsi che una classe abbia solo un'istanza e fornire un punto di accesso globale a essa.

Con il termine Singleton si indica quindi  una modalità di dichiarazione delle classi che ha lo scopo di garantire la creazione di una sola istanza della classe. La creazione e l’accesso a  questa istanza è gestito direttamente dalla classe dichiarata in modalità Singleton.
Gli elementi che caratterizzano un Singleton sono:

  • il costruttore privato, per evitare la creazione di oggetti da classi esterne;
  •  un metodo statico, per accedere all’unica istanza dell’oggetto.

Per chiarirci l’idea riportiamo un esempio di implementazione riportando il caso dello Spooler di una stampante.

implementazione

public class PrinterSpooler {
private static PrinterSpooler instance;
private PrinterSpooler() {
}
public static synchronized PrinterSpooler getInstance() {
if ( instance==null) {
instance = new PrinterSpooler();
}
return instance;
}

}

Il codice è molto semplice:la classe PrinterSpooler permette l’istanziazione di un unico oggetto, tramite l’invocazione del  metodo  getInstance  e non del costruttore il quale è dichiarato volutamente privato. Il metodo getInstance si fa quindi  carico di creare l’unica istanza dell’oggetto; le successive invocazioni di getInstance restituiscono il riferimento allo stesso oggetto. Tale metodo è stato dichiarato synchronized per garantire il corretto utilizzo contemporaneamente da più thread.  La soluzione mostrata ha ripercussioni sulla performance, perché tutte le invocazioni di getInstance()  sono rallentate dall’overhead del syncrhonized; se ci pensate è uno spreco perchè la race condition può accadere solo sul primo utilizzo del metodo getIstance; in letteratura è nota una seconda versione del singleton la quale al costo minimo di un doppio controllo effettuato quando l’istanza non è stata creata , evita che le successive chiamate del  getInstance() incorrano nell’overhead di
sincronizzazione.

public class PrinterSpooler {
    private static volatile PrinterSpooler instance;
    private PrinterSpooler() {
    }
    public static synchronized PrinterSpooler getInstance() {
        if ( instance==null)      {
            synchronized (PrinterSpooler.class) {
                if (instance == null) {
                    instance = new PrinterSpooler();
                }
            }
        }

        return instance;
    }

}

considerazioni

Il pattern appena descritto potrebbe sembrare semplicissimo; in effetti lo è ma bisogna considerare i seguenti aspetti e situazioni in cui non è affatto semplice progettare una classe singleton:

  • Presenza di Singletons in multiple virtual machines.
  • Singletons caricati contemporaneamente da diversi class loaders.
  •  Singletons distrutti dal garbage collector, e dopo ricaricati quando
    sono necessari.
  •  Presenza di multiple istanze come sottoclassi di un Singleton.
  •  Copia di Singletons come risultato di un doppio processo di
    deserializzazione.

Tali punti sono spiegati in dettaglio in un articolo di Oracle che vi consiglio altamente di leggere al seguente LINK

Ora che abbiamo descritto il singleton e una sua possibile implementazione, non voglio soffermarmi a parlare di possibili varianti e implementazioni di tale pattern ( esistono molti libri che già lo fanno) ma voglio parlarvi dei casi di utilizzo e di problemi che potreste scontrarvi quando si abusa del singleton.

LE CRITICHE SULL’UTILIZZO DEL SINGLETON

L’uso di singleton è in realtà un argomento abbastanza controverso nella comunità OOP; quello che una volta era uno schema di progettazione spesso utilizzato viene ora considerato come una pratica da evitare: addirittura un anti-pattern.Le critiche più forti che vengono fatte al singleton sono le seguenti:

  1. Il problema principale con il Singleton Pattern è che viene frainteso e spesso utilizzato per introdurre il concetto di variabili globali nel proprio sistema introducendo nell’applicazione lo stato globale nel dominio dell’applicazione. Questo è generalmente negativo perché le variabili globali non si preoccupano della struttura del programma, dello stato dei suoi thread e del suo flusso di esecuzione.
  2. Nascondere le dipendenze: La maggior parte delle classi singleton in cui  ci imbattiamo sono  incompatibili con i principi generali di progettazione dell’ingegneria del software. Spesso fungono da aggregati per funzionalità diverse (a volte anche non correlate), introducendo in tal modo varie dipendenze e spesso violando concetti fondamentali come quello di responsabilità singola (single responsibility principle, abbreviato con SRP):ovvero che ogni elemento di un programma (classe, metodo, variabile) debba avere una sola responsabilità, e che tale responsabilità debba essere interamente incapsulata dall’elemento stesso. Infatti una delle abilità uniche di un singleton è che è possibile accedervi ovunque tramite il suo metodo statico disponibile globalmente, consentendo ai programmatori di utilizzarlo all’interno di un metodo senza doverlo passare espressamente attraverso i parametri. Anche se questo può sembrare più semplice per il programmatore, fare affidamento su questa istanza statica significa che le firme dei metodi non mostrano più le loro dipendenze, perché il metodo potrebbe estrarre un singleton “dal nulla”. Ciò significa che gli utenti hanno bisogno di conoscere le logiche interne del codice per usarlo correttamente, rendendolo più difficile da usare e testare.
  3. Altro punto negativo è quello che il singleton  viola il Principio di Sostituzione di Liskov in quanto, non consentendo relazioni di ereditarietà, impedisce la sostituibilità di oggetti legati da un vincolo di parentela.

QUINDI ANTI-PATTERN O PATTERN ?

Il lettore a questo punto dell’articolo si farà sicuramente la seguente domanda: siamo di fronte ad un Pattern o Anti-Pattern? La domanda non è semplice, provo a dare il mio pensiero. Io sono d’accordo al 1001 per cento della gravità dei problemi descritti nel paragrafo precedente. La combinazione di questi problemi mostra un altro problema, a mio avviso il più’ grave: nel mondo odierno di sviluppo orientato ai test e agile, è più importante che mai avere piccoli test che coprono la maggior parte del codice. Una delle cose importanti di questi test è che devono essere in grado di essere eseguiti in qualsiasi ordine (non dipendenti l’uno dall’altro), il che può diventare un problema con l’utilizzo di singleton. Poiché le dipendenze di alcuni metodi dati non sono chiare quando si basano su singleton (ricavandole da getter statici), un tester può non sapere scrivere due test che effettivamente dipendono l’uno dall’altro modificando una risorsa condivisa (il singleton). Questo può produrre test di infima qualità, quelli che passano quando vengono eseguiti in un determinato ordine ma falliscono quando vengono eseguiti in un altro.

E’ vero però che tutti questi problemi sono conseguenze di un ABUSO del singleton; il singleton non è nato per modellare uno stato globale ma bensì per contendere istanze uniche di risorse sharabili nel proprio applicativo: mi viene in mente una coda di stampa, il logger, la connessione al db o verso un altro tipo di dispositivo. Capite quindi che i casi corretti di utilizzo del singleton sono ben pochi ma questo non deve significare che tale pattern non è utile poiché la sua casistica di utilizzo esiste. E’ certo però che l’utilizzo al di fuori di queste casistiche diventa un vero e proprio anti-pattern.

A mio avviso esistono altri pattern e tecniche per risolvere casi  di cattivo utilizzo del singleton senza cadere nelle problematiche descritte da questo articolo: la Dependency Injection in primis (di cui io abuso) o il Factory. Vi parlerò di questi pattern nei prossimi articoli.

A proposito di me

Dario Frongillo

Uno degli admin di Italiancoders e dell iniziativa devtalks.
Dario Frongillo è un software engineer e architect, specializzato in Web API, Middleware e Backend in ambito cloud native. Attualmente lavora presso NTT DATA, realtà di consulenza internazionale.
E' membro e contributor in diverse community italiane per developers; Nel 2017 fonda italiancoders.it, una community di blogger italiani che divulga articoli, video e contenuti per developers.

Di Dario Frongillo

Gli articoli più letti

Articoli recenti

Commenti recenti