Singleton (informatica)

Design Pattern nello sviluppo software orientato agli oggetti

Nella programmazione ad oggetti, il singleton è uno dei pattern fondamentali descritti dalla "Gang of Four" nel celebre libro Design Patterns.

Scopo modifica

Il singleton è un design pattern creazionale che ha lo scopo di garantire che di una determinata classe venga creata una e una sola istanza, e di fornire un punto di accesso globale a tale istanza.

 
Diagramma UML di una classe singleton

Implementazione modifica

L'implementazione più semplice di questo pattern prevede che la classe singleton abbia un unico costruttore privato, in modo da impedire l'istanziazione diretta della classe. La classe fornisce inoltre un metodo "getter" statico che restituisce l'istanza della classe (sempre la stessa), creandola preventivamente o alla prima chiamata del metodo, e memorizzandone il riferimento in un attributo privato anch'esso statico. Il secondo approccio si può classificare come basato sul principio della lazy initialization (letteralmente "inizializzazione pigra") in quanto la creazione dell'istanza della classe viene rimandata nel tempo e messa in atto solo quando ciò diventa strettamente necessario (al primo tentativo di uso).

Esempio: Java modifica

Il seguente frammento di codice descrive una classe strutturata secondo il pattern singleton nel linguaggio Java:

public class MioSingolo {
    private static MioSingolo istanza = null;

    //Il costruttore private impedisce l'istanza di oggetti da parte di classi esterne
    private MioSingolo() {}

    // Metodo della classe impiegato per accedere al singleton
    public static synchronized MioSingolo getMioSingolo() {
        if (istanza == null) {
            istanza = new MioSingolo();
        }
        return istanza;
    }
}

Bisogna precisare comunque che questo tipo di approccio risolutivo potrebbe presentare dei difetti (per esempio questa implementazione non è thread safe), infatti il progettista della classe che è chiamato tra l'altro a rispettare il pattern singleton, potrebbe, per esempio, erroneamente dichiarare all'interno della classe un metodo non statico con visibilità public che restituisca un'istanza di un nuovo oggetto della stessa classe, ed ecco che allora il vincolo viene violato in contraddizione a quanto sopra detto.

In definitiva questo significa che in realtà per adempiere pienamente bisogna gestire il tutto con il meccanismo delle eccezioni.

Si possono definire sotto altri metodi, che però non devono essere statici.

Esempio: C++ modifica

Il seguente frammento di codice descrive una classe minimalista strutturata secondo il pattern singleton nel linguaggio C++:

#include <iostream>

class singleton 
{
public:

	static singleton& get_instance() 
    {
        // l'unica istanza della classe viene creata alla prima chiamata di get_instance()
        // e verrà distrutta solo all'uscita dal programma
		static singleton instance;
		return instance;
	}
	
	bool method() 
	{ 
	    return m_something; 
	}
	
protected:
    //contesto del singoletto 
    bool m_something;
    // Il costruttore dichiarato come privato
	singleton() : m_something(false) { }
	// stessa cosa per il costruttore di copia, basta dichiararlo privato, 
	// in quanto viene automaticamente generato dal compilatore 
	// (N.B. eccezion fatta per dllexport, usando MSVC)
	singleton(const singleton&);
	// stessa cosa per l'operatore di assegnamento: basta dichiararlo privato.
    void operator=(const singleton&);
};

int main() 
{
	std::cout << singleton::get_instance().method() << std::endl;
	return 0;
}

Esempio: C++11 modifica

Il seguente frammento di codice descrive una classe minimalista strutturata secondo il pattern singleton nel linguaggio C++11:

#include <iostream>

class singleton 
{
public:

	static singleton& get_instance() 
    {
		static singleton instance;
		return instance;
	}
	
	bool method() 
	{ 
	    return m_something; 
	}
	
protected:
    //contesto del singoletto 
    bool m_something { false };
    
    // costruttore privato
	singleton() { }
    
    //no copy    
    singleton(const singleton&)       = delete;
    void operator= (const singleton&) = delete;
};

int main() 
{
	std::cout << singleton::get_instance().method() << std::endl;
	return 0;
}

Esempio: C# modifica

Il seguente frammento di codice descrive una classe strutturata secondo il pattern singleton nel linguaggio C#:

public class MyClass
{
    //..attributi membro di istanza....
    private static MyClass _instance=null;
    
    protected MyClass()
    {
        //...inizializzazione istanza...
    }
    
    public static MyClass Instance
    {
        get
        {
            if(_instance==null) _instance=new MyClass();
            return _instance;
        }
    }

    //...eventuali metodi pubblici, privati e protetti di istanza....
}

Esempio: Objective-C modifica

Il seguente frammento di codice descrive una classe strutturata secondo il pattern singleton nel linguaggio Objective-C:

#import "Singleton.h"
 
@implementation Singleton
 
static Singleton *sharedSingleton =nil;
 
+ (Singleton *) sharedSingleton 
{ 
  if (sharedSingleton == nil) 
  { 
    sharedSingleton = [[super allocWithZone:NULL] init]; 
  } 
  return sharedSingleton; 
}
 
+ (id)allocWithZone:(NSZone *)zone 
{ 
 @synchronized(self) 
 { 
   if (sharedSingleton == nil) 
   { 
     sharedSingleton = [super allocWithZone:zone]; 
     return sharedSingleton; 
   } 
 } 
 return nil; 
}
 
- (id)copyWithZone:(NSZone *)zone 
{ 
 return self; 
}
 
- (id)retain 
{ 
 return self; 
}
 
- (NSUInteger)retainCount 
{ 
 return NSUIntegerMax; 
}
 
- (void) release 
{ 
}
 
- (id)autorelease 
{ 
 return self; 
} 
@end

Esempio: Swift modifica

Il seguente frammento di codice descrive una classe strutturata secondo il pattern singleton nel linguaggio Swift:

class NetworkManager {

    // MARK: - Properties
    static let shared = NetworkManager(baseURL: API.baseURL)

    // MARK: -
    let baseURL: URL

    // Initialization
    private init(baseURL: URL) {
        self.baseURL = baseURL
    }
}

Esempio: PHP modifica

Il seguente frammento di codice descrive una classe strutturata secondo il pattern singleton nel linguaggio PHP:

class Singleton
{
    private static $instance = null;

    private function __construct()
    {
         //...inizializzazione istanza...
    }

    private function __clone()
    {
        // evita la clonazione dell'oggetto
    }

    public static function getInstance()
    {
        if (static::$instance === null) {
            static::$instance = new Singleton();
        }
        return static::$instance;
    }
}

Esempio: JavaScript Node.js modifica

Il seguente frammento di codice descrive come creare un'istanza singleton di una classe usando JavaScript in Node.js:

class Singleton
{
    constructor() {
         //...inizializzazione istanza...
    }
}

const singletonInstance = null;

module.exports = {
  getInstance: () => {
    if (!singletonInstance) {
      singletonInstance = new Singleton();
    }
    
    return singletonInstance;
  },
};

A differenza degli altri linguaggi di programmazione, in JavaScript, non è possibile definire lo scope "privato" per funzioni e proprietà. Per applicare il pattern in Node.js bisogna agire con l'espediente usato in esempio.

Implementazioni multi-thread modifica

In applicazioni multi-thread l'utilizzo di questo pattern con la lazy initialization richiede un'attenzione particolare: se due thread tentano di eseguire contemporaneamente il costruttore quando la classe non è stata ancora istanziata, devono entrambi controllare se l'istanza esiste e soltanto uno deve creare la nuova istanza.

Sincronizzazione esplicita modifica

Il modo più semplice per implementare una versione thread-safe è quello di usare un meccanismo di sincronizzazione come quello fornito dalla parola chiave synchronized di Java. Tuttavia questo approccio è inefficiente: infatti la sincronizzazione è utile solo per la prima inizializzazione, e costituisce un inutile overhead nelle successive chiamate al metodo getter.

Esempio: Java modifica

public class MioSingleton {
    //VOLATILE garantisce che i cambiamenti siano visti immediatamente da tutti gli altri thread
    private volatile static MioSingleton istanza = null;
 
    //Il costruttore private impedisce l'istanza di oggetti da parte di classi esterne
    private MioSingleton() {}
 
    // Metodo della classe impiegato per accedere al singleton
    public static MioSingleton getMioSingleton() {
        if (istanza == null) {
            //posso sincronizzare solo questa parte del metodo perché l'istanza è di tipo volatile
            synchronized (MioSingleton.class){
                //non sono sicuro di essere ancora il primo thread ad accedere al metodo, quindi ricontrollo
                if (istanza == null)
                    istanza = new MioSingleton();
            }
        }
        return istanza;
    }
}

Esempio: C# modifica

Ed eccone la traduzione in codice C#. Anche questa implementazione è thread-safe, ed ugualmente inefficiente per l'utilizzo del lock sull'oggetto che funge da semaforo (verificando se l'istanza è null anche prima del lock[1], è possibile eliminare questa inefficienza):

public class Singleton 
{
    private static Singleton istanza=null;
    private static object semaforo = new object();

    private Singleton() {}

    public static Singleton Istanza 
    {
      get {
        lock(semaforo) {
          if(istanza==null) istanza=new Singleton();
            return istanza;
        }
      }
    }
}

Sincronizzazione implicita modifica

In alcuni linguaggi è possibile evitare l'overhead di sincronizzazione sfruttando quelle peculiarità della lazy initialization che consentono di assicurarsi la presenza del singleton in memoria all'atto del suo utilizzo. Le modalità specifiche possono variare da linguaggio a linguaggio; ad esempio, in Java è possibile sfruttare il fatto che l'inizializzazione di una classe ed il suo caricamento in memoria, quando avvengono, sono operazioni thread-safe che comprendono l'inizializzazione di tutte le variabili statiche (attributi) della classe stessa.

Quello che segue è l'esempio più semplice, che tuttavia realizza la creazione dell'istanza al momento dell'inizializzazione della classe (ad esempio, invocando un metodo statico della classe stessa). Questo approccio è adatto nei casi più semplici, o in quei casi in cui la lazyness non è necessaria; ad esempio, è sconsigliato in applicazioni in cui sono presenti numerosi singleton dall'inizializzazione "pesante" dotati di metodi o attributi statici che potrebbero essere acceduti in largo anticipo rispetto all'effettiva necessità d'uso del singleton in quanto tale (tipicamente, all'avvio dell'applicazione).

Esempio di inizializzazione preventiva: Java modifica

public class Singleton { 
   
   /**
    * Creato all'atto di caricamento in memoria della classe, thread-safe
    */
   private final static Singleton ISTANZA = new Singleton();

  /**
   * Costruttore privato, in quanto la creazione dell'istanza deve essere controllata.
   */
  private Singleton() {}
 
  /**
   * Punto di accesso al Singleton.
   * @return il Singleton corrispondente
   */
  public static Singleton getInstance() {
    return ISTANZA;
  }
}

Esempio di inizializzazione lazy: Java modifica

Un approccio che rimanda la creazione del singleton al suo effettivo primo utilizzo è stato presentato per la prima volta da Bill Pugh, e sfrutta appieno la lazy initialization: l'idea è quella di includere nella classe che implementa il singleton una classe-contenitore avente, come attributo statico, una istanza del singleton stesso: il primo accesso a tale attributo statico (e la contestuale inizializzazione) verrà quindi effettuato durante l'inizializzazione della classe-contenitore, e quindi sempre in modo serializzato. In questo modo l'istanza del singleton viene creata solo alla prima chiamata del metodo getter, e non prima.

public class Singleton {

  /**
   * Costruttore privato, in quanto la creazione dell'istanza deve essere controllata.
   */
  private Singleton() {}
 
  /**
   * La classe Contenitore viene caricata/inizializzata alla prima esecuzione di getInstance()
   * ovvero al primo accesso a Contenitore.ISTANZA, ed in modo thread-safe.
   * Anche l'inizializzazione dell'attributo statico, pertanto, viene serializzata.
   */
  private static class Contenitore { 
    private final static Singleton ISTANZA = new Singleton();
  }
 
  /**
   * Punto di accesso al Singleton. Ne assicura la creazione thread-safe
   * solo all'atto della prima chiamata.
   * @return il Singleton corrispondente
   */
  public static Singleton getInstance() {
    return Contenitore.ISTANZA;
  }
}

Esempi: Vb.Net modifica

Public Class UnsafeSingleton
    Private Shared _instance As New UnsafeSingleton
    Private Sub New() : End Sub
    Public Shared ReadOnly Property INSTANCE As UnsafeSingleton
        Get
                Return _instance
        End Get
    End Property
End Class

'Singleton Thread-safe
Public Class Singleton
    Private Shared _instance As New Singleton
    Private Shared _threadsafe As Object = New Object()
    Private Sub New() : End Sub
    Public Shared ReadOnly Property INSTANCE As Singleton
        Get
            SyncLock _threadsafe
                Return _instance
            End SyncLock
        End Get
    End Property
End Class

'Singleton thread-safe che sfrutta la lazy initialization
Public Class LazySingleton
    Private Shared _instance As LazySingleton = Nothing
    Private Shared _threadsafety As Object = New Object()
    Private Sub New() : End Sub
    Public Shared ReadOnly Property INSTANCE As LazySingleton
        Get
            SyncLock _threadsafety
                If _instance Is Nothing Then _instance = New LazySingleton()
                Return _instance
            End SyncLock
        End Get
    End Property
End Class

Critiche modifica

Alcuni autori hanno criticato il pattern singleton, osservando che, con opportune modifiche strutturali, una istanza singola può entrare più efficacemente a far parte dell'Ambiente globale dell'applicazione[2].

Note modifica

  1. ^ MSDN. Implementing Singleton in C#, Version 1.0.1
  2. ^ Scott Densmore. Why singletons are evil, May 2004

Voci correlate modifica

Altri progetti modifica

Collegamenti esterni modifica

  Portale Informatica: accedi alle voci di Wikipedia che trattano di informatica