Singleton pattern: una classe di per sé

I cosiddetti design pattern mettono a disposizione degli sviluppatori molteplici modelli comprovati in grado di risolvere i passaggi più ostici della programmazione orientata agli oggetti. Una volta trovato il modello di design che fa al caso proprio tra i circa settanta a disposizione, bastano alcuni accorgimenti 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 performante, è reputato ad oggi alquanto obsoleto nell’ambito della programmazione orientata agli oggetti. In questo articolo vi spieghiamo quello che permette di fare, quando e come impiegarlo.

Che cos’è il singleton pattern?

Il singleton pattern appartiene alla categoria dei modelli creazionali 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 desiderato viene creato all’interno di una classe per poi essere invocato come istanza statica.

Citazione

Il team di programmatori americano “Gang of Four” (GoF) ha dichiarato quanto segue riguardo al singleton pattern: “Assicuratevi che una classe possegga esattamente un’istanza e che la renda accessibile globalmente.”

Quali sono le caratteristiche 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 accessibile globalmente all’interno del software, tramite uno dei vari metodi disponibili nei linguaggi di programmazione. Per assicurarsi che l’istanza creata rimanga l’unica, bisogna impedire che l’utente possa crearne di nuove. Il costruttore deve quindi dichiarare il modello “private”. In questo modo solamente il codice contenuto nel singleton può istanziare lo stesso singleton. Questo garantisce che l’utente ottenga un solo e unico oggetto, sempre lo stesso. Quando questa istanza viene resa disponibile, 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 rappresentato in UML

La rappresentazione dell’intero singleton design pattern con il linguaggio di modellazione unificato, abbreviato 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’operazione veloce e poco complicata, in quanto non consta di un numero elevato di variabili (globali). Incapsula la propria creazione così da poter esercitare pieno controllo su quando e come sia possibile accedervi. Un singleton pattern esistente può essere declinato per mezzo di sottoclassi per eseguire nuove funzionalità. Cosa utilizzare viene deciso in maniera dinamica.

Non di secondaria importanza è il fatto che un singleton venga creato solamente quando necessario. Questa caratteristica prende il nome di lazy loading. Eager loading, al contrario, definisce il processo di istanziare un singleton in maniera anticipata, ossia quando non ancora necessario.

Gli svantaggi in breve

L’uso sconsiderato dei singleton porta a uno stato di programmazione procedurale, quindi non più orientata agli oggetti, e a un codice di programmazione che possiamo definire “sporco”. La disponibilità 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 componenti del programma siano interessate. Questo complica la manutenzione del software, poiché risulta difficile risalire ai malfunzionamenti.

La disponibilità globale rende altrettanto complicata l’eliminazione dei singleton, in quanto alcune componenti del software possono riferirsi a questo. Nel caso di applicazioni con molti utenti (applicazioni multiutente), un singleton può avere l’effetto di diminuire le prestazioni, 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 utilizzato spesso quando risulta necessario risolvere processi molto ripetitivi 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 meccanismi di cache usano solitamente gli stessi processi, anche in questo caso il singleton design pattern trova spesso impiego.

Data la difficoltà di testare un singleton pattern, vi illustriamo qui di seguito il suo funzionamento tramite l’esempio di una piccola azienda nella quale numerosi dipendenti utilizzano 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’indicazione di carattere if/then (return Stampante == Null ?). Inoltre, per evitare accessi e modifiche indesiderati vengono impiegate delle variabili specifiche 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 dipendenti dell’azienda vengono “incapsulati”, ossia isolati. Le stringe di dati contenenti 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 ();
	}
}
Per offrirti una migliore esperienza di navigazione online questo sito web usa dei cookie, propri e di terze parti. Continuando a navigare sul sito acconsenti all’utilizzo dei cookie. Scopri di più sull’uso dei cookie e sulla possibilità di modificarne le impostazioni o negare il consenso.