I co­sid­det­ti design pattern mettono a di­spo­si­zio­ne degli svi­lup­pa­to­ri mol­te­pli­ci modelli com­pro­va­ti in grado di risolvere i passaggi più ostici della pro­gram­ma­zio­ne orientata agli oggetti. Una volta trovato il modello di design che fa al caso proprio tra i circa settanta a di­spo­si­zio­ne, bastano alcuni ac­cor­gi­men­ti per un risultato ottimale. L’approccio di base di ciascun modello rimane però sempre lo stesso. Il singleton pattern o modello di design singleton, sebbene molto per­for­man­te, è reputato ad oggi alquanto obsoleto nell’ambito della pro­gram­ma­zio­ne orientata agli oggetti. In questo articolo vi spie­ghia­mo quello che permette di fare, quando e come im­pie­gar­lo.

Che cos’è il singleton pattern?

Il singleton pattern ap­par­tie­ne alla categoria dei modelli crea­zio­na­li ed è uno dei pattern più semplici ma anche più potenti per lo sviluppo software. Il suo compito consiste nell’impedire che da una classe possa essere creato più di un oggetto. Per farlo, l’oggetto de­si­de­ra­to viene creato all’interno di una classe per poi essere invocato come istanza statica.

Citazione

Il team di pro­gram­ma­to­ri americano “Gang of Four” (GoF) ha di­chia­ra­to quanto segue riguardo al singleton pattern: “As­si­cu­ra­te­vi che una classe possegga esat­ta­men­te un’istanza e che la renda ac­ces­si­bi­le glo­bal­men­te.”

Quali sono le ca­rat­te­ri­sti­che del singleton pattern?

Creando un’istanza da una classe con il singleton design pattern ci si assicura che non ne vengano create delle altre. Il singleton rende questa classe ac­ces­si­bi­le glo­bal­men­te all’interno del software, tramite uno dei vari metodi di­spo­ni­bi­li nei linguaggi di pro­gram­ma­zio­ne. Per as­si­cu­rar­si che l’istanza creata rimanga l’unica, bisogna impedire che l’utente possa crearne di nuove. Il co­strut­to­re deve quindi di­chia­ra­re il modello “private”. In questo modo solamente il codice contenuto nel singleton può istan­zia­re lo stesso singleton. Questo ga­ran­ti­sce che l’utente ottenga un solo e unico oggetto, sempre lo stesso. Quando questa istanza viene resa di­spo­ni­bi­le, non ne vengono create di nuove. Un singleton si presenta così:

public class Singleton {
	private static Singleton instance; // statico e protetto da accesso esterno
	private Singleton() {} // costruttore privato con protezione da accesso esterno
	public static Singleton getInstance() { // metodo aperto, invocazione tramite codice
		if (instance == null) { // solamente quando non esiste alcuna istanza, ne crea una nuova
			instance = new Singleton();
		}
		return instance;
	}
}

Il singleton pattern rap­pre­sen­ta­to in UML

La rap­pre­sen­ta­zio­ne dell’intero singleton design pattern con il lin­guag­gio di mo­del­la­zio­ne unificato, ab­bre­via­to in UML (Unified Modeling Language), consiste di un unico oggetto, in quanto si tratta della creazione di un’unica istanza di una classe.

Dall’esterno non è possibile apportare modifiche all’unica istanza generata. È proprio questa la finalità d’utilizzo del singleton design pattern.

Vantaggi e svantaggi del modello di design singleton

I vantaggi in breve

Scrivere un singleton è un’ope­ra­zio­ne veloce e poco com­pli­ca­ta, in quanto non consta di un numero elevato di variabili (globali). Incapsula la propria creazione così da poter eser­ci­ta­re pieno controllo su quando e come sia possibile accedervi. Un singleton pattern esistente può essere declinato per mezzo di sot­to­clas­si per eseguire nuove fun­zio­na­li­tà. Cosa uti­liz­za­re viene deciso in maniera dinamica.

Non di se­con­da­ria im­por­tan­za è il fatto che un singleton venga creato solamente quando ne­ces­sa­rio. Questa ca­rat­te­ri­sti­ca prende il nome di lazy loading. Eager loading, al contrario, definisce il processo di istan­zia­re un singleton in maniera an­ti­ci­pa­ta, ossia quando non ancora ne­ces­sa­rio.

Gli svantaggi in breve

L’uso scon­si­de­ra­to dei singleton porta a uno stato di pro­gram­ma­zio­ne pro­ce­du­ra­le, quindi non più orientata agli oggetti, e a un codice di pro­gram­ma­zio­ne che possiamo definire “sporco”. La di­spo­ni­bi­li­tà globale dei modelli di design singleton cela dei pericoli nel caso in cui serva a trattare dati sensibili. Quando vengono apportate modifiche al singleton non è dato sapere quali com­po­nen­ti del programma siano in­te­res­sa­te. Questo complica la ma­nu­ten­zio­ne del software, poiché risulta difficile risalire ai mal­fun­zio­na­men­ti.

La di­spo­ni­bi­li­tà globale rende al­tret­tan­to com­pli­ca­ta l’eli­mi­na­zio­ne dei singleton, in quanto alcune com­po­nen­ti del software possono riferirsi a questo. Nel caso di ap­pli­ca­zio­ni con molti utenti (ap­pli­ca­zio­ni mul­tiu­ten­te), un singleton può avere l’effetto di diminuire le pre­sta­zio­ni, in quanto non permette un flusso regolare dell’intera mole di dati, creando un’impasse.

Il modello di design singleton nella realtà

Il singleton viene uti­liz­za­to spesso quando risulta ne­ces­sa­rio risolvere processi molto ri­pe­ti­ti­vi nella routine di un programma. Ad esempio: per scrivere dati all’interno di un file, nel caso si tratti di file di registro o per ordini di stampa che devono essere eseguiti nello stesso buffer. Dato che anche i driver e i mec­ca­ni­smi di cache usano so­li­ta­men­te gli stessi processi, anche in questo caso il singleton design pattern trova spesso impiego.

Data la dif­fi­col­tà di testare un singleton pattern, vi il­lu­stria­mo qui di seguito il suo fun­zio­na­men­to tramite l’esempio di una piccola azienda nella quale numerosi di­pen­den­ti uti­liz­za­no la stessa stampante. Un esempio che si avvicina alla pratica reale lo fornisce la serie tutorial di Daniel H. Jacobsen, sulla quale si basa il nostro esempio.

Quando un utente invia una richiesta alla stampante, il singleton avanza questa domanda: “esiste già un oggetto stampante? Se la risposta è no, allora deve essere creato”. Per farlo si utilizza un’in­di­ca­zio­ne di carattere if/then (return Stampante == Null ?). Inoltre, per evitare accessi e modifiche in­de­si­de­ra­ti vengono impiegate delle variabili spe­ci­fi­che e la stampante impostata come “private” (privata).

public class Stampante {
	private static Stampante stampante;
	private int Numeropagine;
	private Stampante() {
	}
	public static Stampante getInstance() {
		return stampante == Null ? 
				stampante = new Stampante() : 
				stampante;
	}
	public void print(String text){
		System.out.println(text +
				"\n" + "Numero di pagine stampate oggi" + ++ Numeropagine+
				"\n" + "---------");
	}
}

Dopodiché i singoli di­pen­den­ti dell’azienda vengono “in­cap­su­la­ti”, ossia isolati. Le stringe di dati con­te­nen­ti nomi, posizione e funzione svolta in azienda vengono resi privati.

public class Dipendente {
	private final String nome;
	private final String posizione;
	private final String funzione;
	public Dipendente(String nome, String posizione, String funzione) {
		this.nome = nome;
		this.posizione = posizione;
		this.funzione = funzione;
	}
	public void printCurrent funzione (){
		Stampante stampante = Stampante.getInstance();
		stampante.print("Dipendente: " + nome + "\n" +
			"Posizione: " + posizione + "\n" +
			"Funzione: " + funzione + "\n");
	}
}

Infine, i due singleton vengono integrati in una routine di output.

public class Main {
	public static void main(String[] args) {
		Dipendente andrea= new Dipendente ("Andrea",
				"Capo",
				"Amministra l’azienda");
		Dipendente giulia = new Dipendente ("Giulia",
				"Consulente",
				"Assiste i clienti nel caso di reclami");
		Dipendente tommaso = new Dipendente ("Tommaso",
				"Commerciale",
				"Vende i prodotti");
		Dipendente stefania = new Dipendente ("Stefania",
				"Sviluppatrice",
				"Esegue la manutenzione dell’infrastruttura IT dell’azienda.");
		Dipendente mattia = new Dipendente ("Mattia",
				"Contabile",
				"Tiene i libri contabili dell’azienda.");
		andrea.printCurrentFunzione ();
		giulia.printCurrentFunzione ();
		tommaso.printCurrentFunzione ();
		stefania.printCurrentFunzione ();
		mattia.printCurrentFunzione ();
	}
}
Vai al menu prin­ci­pa­le