Java: Introduzione Ai Design Pattern: Singleton

by Alessio Mungelli Date: 11-09-2019 java programming programmazione pattern design sviluppo tech singleton


Chiunque abbia anche una minima esperienza di programmazione, si sarà reso conto di come i problemi
sianoricorrenti. Infatti troviamo spesso problemi con uno stesso pattern ma con contesti differenti.
Ad esempio, un gestionale per un magazzino di un supermercato e quello per un magazzino di una grande industria avranno presumibilmente dati diversi, ma il comportamento del programma sarà pressochè lo stesso.
Allargando questo tipo di concetto a tutta la programmazione, si può affermare che i
problemi incontrati nello sviluppare grossi progetti software sono spesso ricorrenti e prevedibili.
Questo tipo di ragionamento da luogo ai pattern, meglio conosciuti come design pattern.

Cosa significa usare un pattern?

Usare un pattern significa non inventare da capo nuove soluzioni, usando soluzioni già create e consolidate nel tempo. L'idea è quindi quella di fornire un vocabolario "comune" alla maggior parte dei progettisti.

Dando una definizione più teorica, un pattern è un insieme di classi e oggetti comunicanti adattabili per risolvere un problema ricorrente in un contesto specifico.

La cosa importante è che sono stati concepiti come non domain-specific, per cui non sono rivolti ad applicazioni specifiche, ma sono riutilizzabili in parti di applicazioni diverse.

Questo tipo di strategia di progettazione viene anche usato in classi Java che usiamo comunemente, come Abstract, Iterator, Factory, Singleton.

Ad oggi, i pattern possono essere classificati come:

  • pattern crazionali:riguardano il processo di creazione degli oggetti;
  • pattern strutturali:hanno a che fare con l'aspetto di composizione delle classi e degli oggetti;
  • pattern comportamentali: si occupano dell'aspetto riguardante l'interazione e la distribuzione della responsabilità tra le classi;

Un esempio molto semplice: Singleton

La necessità di questo tipo di design pattern nasce dal fatto che si potrebbe aver bisogno di una classe con una sola istanza.

Possibilmente, l'implementazione deve essere thread-safe. Vediamo meglio.

Implementazione classica

Ecco il diagramma UML:

Singleton

public class Singleton{
/*
Questa sara' l'unica istanza di questa classe.
Ogni accesso avverrà tramite quest oggetto
e non ne verranno creati altri.
*/
private static Singleton instance = null; 
/*
Costruttore privato per evitare la creazione di altre
istanze dell'oggetto
*/
private Singleton(){
}
/*
Unico punto di accesso all'istanza
*/
public static Singleton factory(){
//creo l'oggetto se non esiste
if(instance==null)
instance = new Singleton();
return instance;
}
//eventuali altri metodi
}

Questo tipo di implementazione non è ottimale per un ambiente multithread. Vediamo una possibile modifica

public class Singleton{
/*
Questa sara' l'unica istanza di questa classe.
Ogni accesso avverrà tramite quest oggetto
e non ne verranno creati altri.
*/
private static Singleton instance = null; 
/*
Costruttore privato per evitare la creazione di altre
istanze dell'oggetto
*/
private Singleton(){
}
private synchronized static Singleton createInstance(){
if(instance==null)
instance=new Singleton();
return instance;
}
public static Singleton factory(){
if(instance==null)
//richiamo il metodo synchr solo se 
//l'istanza non esiste
createInstance();
return instance;
}
//eventuali altri metodi
}

Capiamo il codice

I due esempi riportati svolgono pressochè le stesse funzioni se pur con modalità leggermente diverse.

Il primo esempio ha un attributo statico che rappresenta l'unica istanza accessibile della classe. Si è deciso di rendere il costruttore privato in modo tale da impedire la creazione di istanze indesiderate della classe.

L'accesso alla classe può essere fatto solo mediante un unico metodo factory che controlla lo stato dell'attributo statico. Nel caso in cui non esistesse, lo istanzia. Il metodo termina restituendo al chiamante l'attributo.

Ci saranno quindi due casi possibli:

  • instance == null: il meteodo istanzierà instance e la restituirà al chiamante;
  • instance != null: il metodo restituirà al chiamante il puntatore ad instance.

A prima vista l'implementazione è relativamente facile per chiunque abbia un minimo di manualità. In un ambiente multithread potrebbero sorgere problemi di sincronizzazione, per cui si è deciso di realizzare una versione thread-safe.

La logica del secondo esempio è la medesima del primo, così come il risultato finale, se pur con una logica applicativa leggermente diversa.

Si nota subito che qui i metodi sono due e non più uno. Il primo metodo è necessariamente synchronized al fine di garantire una corretta sincronizzazione tra i vari thread.

Ricordiamo che un metodo synchronized è un metodo tale che quando un thread richiama il metodo synchronized su un oggetto, tutti gli altri thread che richiamano quel metodo dopo sospendono la loro esecuzione fino a che non termina l’esecuzione del metodo (richiamato dal primo thread).

Il metodo createInstance() ha il compito di controllare lo stato dell'attributo statico e in base al suo valore, istanziarlo o no.

Il metodo factory si limita a fare da wrapper a createInstance() controllando anche lo stato di instance.

Questo doppio controllo, che può sembrare apparentemente insensato, serve a diminuire l'overhead. Strategie come questa vengono definite double-checked locking.

Volendo ci sono anche altre strategia man mano più complesse per implementare un Singleton, che diminuiscono man mano l'overhead.

Una possibile alternativa è la seguente:

public class Singleton{
	private static Singleton instance = null; 
	private Singleton(){
	}
	public static Singleton getIstance() {
        if(istance==null)
                synchronized(Singleton.class) {
                        if( instance == null )
                                istance = new Singleton();
                        }
        return istance;
}
	//eventuali altri metodi
}
 

Bisogna dire anche che oggi vengono mosse diverse critiche verso questo pattern, perchè il problema del Singleton Pattern 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 è un male, perchè le variabili gloabli notoriamente non si preoccupano della stuttura del programma.

Un'altra critica piuttosto forte che viene mossa è che la maggior parte delle classi Singleton sono contro i principi generali dell'ingengeria del software, funzionando quasi da aggreganti per funzionalità diverse spesso senza relazioni, introducendo così il concetto di dipendenza, violando il concetto di responsabilità singola.

Infatti, secondo i principi dell'ingengeria del software, ogni elemento del programma deve avere una sola responsabilità e tale responsabilità deve essere interamente incapsulata dall'elemento stesso.

Il Singleton permette di accedervi mediante un unico metodo statico e questa caratteristica rende possibile l'utilizzo dell'istanza all'interno di altri metodi senza passare attraverso i parametri. Per quanto possa sembrare comodo, questo significa che le signature dei metodi non mostrano più le dipendenze, rendendo necessaria agli utenti la conoscenza delle logiche interne del codice. In questa maniera il software sarà nettamente più difficile da usare e testare.

Un terzo difetto è la violazione del Principio di Sostituzione di Liskov. Infatti, non presentando relazioni di ereditarietà, è impossibile sostituire gli oggetti legati da un vincolo di parentela.

Nonostante le critiche, se non abusato il Singleton può rappresentare una risorsa, anche se vedremo più avanti altre strategie per risolvere problemi analoghi.

 
by Alessio Mungelli Date: 11-09-2019 java programming programmazione pattern design sviluppo tech singleton visite : 1578  
 
Alessio Mungelli

Alessio Mungelli

Computer Science student at UniTo (University of Turin), Network specializtion, blogger and writer. I am a kind of expert in Java desktop developement with interests in AI and web developement. Unix lover (but not Windows hater). I am interested in Linux scripting. I am very inquisitive and I love learning new stuffs.

 
 
 

Articoli correlati

    Un approccio a Java: Il ciclo while

    Ciao a tutti e bentornati! Dopo aver fatto una breve, ma corposa, introduzione sui cicli, andiamo oggi a vedere finalmente le prime implementazioni che utilizzano quello che abbiamo definito ciclo precondizionale. In Java, come…

    Un approccio a Java: I cicli - Introduzione

    Ciao a tutti e bentornati! Sino ad ora, abbiamo parlato di variabili e di strutture di selezione, andando a considerare alcuni degli aspetti fondamentali di questi due concetti. Teoricamente, per…

    Un approccio a Java: strutture di selezione - casi d'uso

    Ciao a tutti e bentornati! Sino ad ora ci siamo preoccupati di fare una carrellata quanto più completa riguardo i concetti fondamentali di cui abbiamo bisogno per approcciarci all'utilizzo delle…

    Un approccio a Java: operatori booleani

    La volta precedente, abbiamo ampiamente parlato delle variabili booleane, cercando di delineare quali siano le principali operazioni che si possono effettuare proprio a livello pratico.  Di tutti i casi esaminati, non abbiamo…

    Un approccio a Java: le variabili booleane

    Ciao a tutti e bentornati! La volta precedente, ho fatto un'introduzione alle strutture condizionali, definendo il loro funzionamento. Prima di poter dare qualche esempio pratico, è necessario chiarire in che modo ci…

    Un approccio a Java: strutture condizionali

    Ciao a tutti e bentornati! Le volte precedenti abbiamo introdotto il concetto di variabile, tentando di definire alcuni concetti basilari a riguardo.  Alcune situazioni fanno però intuire come il solo concetto…

    Un approccio a Java: Le variabili - caso d'uso

    Ciao a tutti amici e bentornati! Dopo l'introduzione fatta sulle variabili, cerchiamo di analizzare alcune criticità che si possono presentare in situazioni alquanto comuni. Partiamo quindi analizzando degli esempi pratici.  Esempio 1: divisione…

    Un approccio a Java: Le variabili

    Ciao a tutti e bentornati! Quest'oggi inizieremo un percorso che ci porterà a studiare, ed eventualmente ripassare, quelle che sono le basi della programmazione. Inizieremo parlando di variaibli. Introduzione Chiunque voglia approcciarsi al…

    Hashmap con concatenamento: liste di trabocco

    In questa breve serie di articoli andremo a vedere com'è possibile realizzare in C la struttura dati Hashmap. Nell'implementazione andremo ad usare le liste doppiamente concatenate come strutture dati ausiliarie. Andiamo…

    Lo sport ai tempi del COVID19

    In questi tempi in cui siamo costretti tutti in casa, il boom del FAI DA TE, soprattutto nello sport, è un must. Ci siamo riscoperti  panettieri, cuochi, artigiani, e perchè…

    Java Strutture Dati: Liste Concatenate

    Con il 2020 andiamo ad esaminare un nuovo aspetto della programmazione: le strutture dati. Spesso capita a tutti di utilizzare strutture messe a disposizione dai vari linguaggi di programmazione. L'obiettivo sarà…

    Java Algoritmi di Ordinamento: Selection Sort

    Andiamo oggi ad analizzare in dettaglio un algoritmo di ordinamento non molto efficiente ma piuttosto utilizzato in diversi ambiti. Stiamo parlando del Selection Sort. Vediamo meglio in dettaglio. Intuizione L'idea alla base è quella…

ALL USERSUSER