Singleton (informatica)
Nella programmazione ad oggetti, il singleton è uno dei pattern fondamentali descritti dalla "Gang of Four" nel celebre libro Design Patterns.
Scopo
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.
Implementazione
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
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++
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
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#
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
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
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
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
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
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
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
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#
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
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
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
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
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
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
- ^ MSDN. Implementing Singleton in C#, Version 1.0.1
- ^ Scott Densmore. Why singletons are evil, May 2004
Voci correlate
Altri progetti
- Wikimedia Commons contiene immagini o altri file sul singleton
Collegamenti esterni
- (EN) §12.4 of Java Language Specification (JLS) Sulla serializzazione nell'inizializzazione delle classi
- (EN) dp4j - Injects Reflection and Patterns Boilerplate Code Su come implementare i singleton in Java e testarli con la Reflection API
- Esempio C# Esempio di implementazione del pattern singleton in C#
- Singleton in Java, Python, su talkera.org.cp-in-1.webhostbox.net (archiviato dall'url originale il 29 marzo 2014).