Potrebbe risultare strana la scelta di presentare come primo pattern della serie proprio il Singleton, definito da molti l’anti-pattern per eccellenza. Sebbene esistano pattern più evoluti, ho ritenuto didatticamente corretto inserire il Singleton in questa serie per i seguenti motivi:
- Semplicità: Si tratta del pattern più semplice da apprendere;
- Concettualmente importante: Ci permette di anticipare in maniera più leggera concetti più evoluti;
- Laravel 5: Molte classi all’interno del core di Laravel seguono i principi base del pattern;
Classificazione di Appartenenza
Si tratta di un pattern di tipo creazionale di oggetti. Se hai letto già l’introduzione, saprai cosa questo voglia dire. Le istanze di una classe Singleton verranno generate in runtime.
Obiettivo
Assicurarsi che una classe, per tutta la durata di esecuzione di un modulo, abbia una ed una sola istanza. Fornire un unico punto di accesso-creazione di tale istanza. In PHP questo concetto potrebbe confondere lo sviluppatore. In effetti, la persistenza dell’istanza di un oggetto non va riferita all’intero ciclo di esecuzione di un’applicazione, ma all’esecuzione di un singolo task, come ad esempio la richiesta HTTP di uno specifico route.
Applicabilità
Il Singleton deve essere usato quando:
- deve esistere una sola istanza di classe, accessibile all’interno del sistema da un punto ben precisato;
- quando l’istanza può essere subclassata da una sottoclasse che ne estenderà le funzionalità senza perderne la caratteristica di unicità e senza modificarne il codice;
Partecipanti
La sola classe che implementa le logiche Singleton. Tale classe:
- è responsabile della creazione di se stessa. Definisce un attributo statico (ovvero condiviso da tutte le istanze) che se diverso da NULL conterrà l’istanza di classe;
- definisce un metodo statico che permetta ai client di accedere alla sua unica istanza;
- annulla le istanze create con operatore “new” e annulla le operazioni di clonazione;
Collaborazione
I client che utilizzano la classe Singleton possono accedere all’istanza di classe esclusivamente tramite il metodo statico getInstance.
Conseguenze
Il singleton presenta diversi vantaggi, fra cui:
- Permette di controllare l’accesso all’istanza unica di classe. Dato che la classe Singleton ingloba se stessa, ha pieno controllo di come questa istanza viene usata.
- Si evitano variabili globali e procedurali, semplificando il namespace.
- Permette il raffinamento di operazioni successive. La classe singleton può infatti essere estesa mantenendo la propria caratteristica di unicità anche nella sottoclasse.
Struttura
La struttura del singleton, nella sua forma base, risulta essere molto semplice. Essa è costituita essenzialmente da un attributo che rappresenti l’unica istanza di classe, 3 magic methods privati (costruttore, operatore di clonazione e wakeup) e un metodo pubblico statico per recuperare l’unica istanza di classe. Rendendo questi metodi privati si previene l’istanziazione della classe Singleton dall’esterno.
Ecco il semplice UML di riferimento:
Implementazione
Il Singleton è davvero il pattern più semplice da apprendere. Per rappresentarlo è infatti sufficiente la seguente classe:
configs;
}
/**
* Sample method to set settings.
*
* @param $key
* @param $value
* @return bool
*/
public function setConfigs(array $configs) {
$this->configs = $configs;
}
}
Analizzando questa classe, oltre ai due metodi di esempio (getter e setter per l’array delle configurazioni) puoi notare l’attributo privato _instance dedicato all’istanza e il metodo getInstance già citati in precedenza.
Ipotizziamo ora di voler ampliare le funzionalità della classe aggiungendo, ad esempio, l’inizializzazione di configs in fase di creazione dell’istanza e dei metodi getter e setter per la gestione della singola configurazione:
configs = $configs;
}
private function __clone(){}
private function __wakeup(){}
/**
* Static Method key of Singleton Pattern, here we
* create/get the unique class instance.
*
* @return Config|null
*/
public static function getInstance( array $configs = array() )
{
if (is_null(static::$instance))
return static::$instance = new static($configs);
$configs = array_merge(
static::$instance->getConfigs(),
$configs
);
static::$instance->setConfigs($configs);
return static::$instance;
}
/**
* Sample method to get generic $key configs.
*
* @param $key
* @return bool
*/
public function getConfigs() {
return $this->configs;
}
/**
* Sample method to set settings.
*
* @param $key
* @param $value
* @return bool
*/
public function setConfigs(array $configs) {
$this->configs = $configs;
}
}
Le modifiche importanti riguardano qui il metodo costruttore, che adesso prevede l’inizializzazione dell’attributo configs, e il metodo getInstance per creare/recuperare l’istanza di classe.
N.B. Nota qui la sostituzione della keyword self con static. La differenza principale sta nel fatto che la classe self richiama sempre la super-classe di riferimento, mentre la classe static fa riferimento alla specifica estensione di classe, rendendo il codice aperto ad estensioni e chiuso a modifiche (Open-Closed SOLID Principle).
“Se noi possiamo cambiare, tutto il mondo può cambiare.””
La piccola modifica alla classe Config mi permette di chiarire un fatto importante:
Un design pattern non è una regola ferrea ideata per ostacolare la vostra creatività e, seguendo i giusti principi, può facilmente essere alterato.
Durante i tuoi studi scoprirai persino che i design pattern possono dipendere o essere combinati fra loro, ovvero possono essere composti. La Gang Of Four, nel libro Design Pattern – Elementi per il riuso di software ad Oggetti, descrive le relazione di 23 differenti design pattern.
Nota come Facade e Factory facciano capo al Singleton!
Testing del Pattern
Uno dei difetti principali del pattern è l’aumentata difficoltà d’uso in fase di testing. Sebbene questo sia vero per situazioni più complesse, ritengo che nella maggior parte dei casi sia possibile effettuare dello unit testing di base.
assertInstanceOf(‘AppConfigConfig’, $firstInstance);
}
public function testUniquenessOfInstance()
{
$firstInstance = Config::getInstance();
$this->assertInstanceOf(‘AppConfigConfig’, $firstInstance);
$secondInstance = Config::getInstance();
$this->assertSame($firstInstance, $secondInstance);
}
public function testPrivateConstructor()
{
$obj = Config::getInstance();
$reflectionObject = new ReflectionObject($obj);
$method = $reflectionObject->getMethod(‘__construct’);
$this->assertTrue($method->isPrivate());
}
public function testPrivateClonation()
{
$obj = Config::getInstance();
$reflectionObject = new ReflectionObject($obj);
$method = $reflectionObject->getMethod(‘__clone’);
$this->assertTrue($method->isPrivate());
}
}
I Singleton e Laravel 5
Ed eccoci arrivati al punto più importante dell’articolo, ovvero l’associazione fra il pattern Singleton e il nostro amato framework. Se hai appreso le ultime modifiche apportate alla classe Config, ritengo che non troverai parecchie difficoltà ad analizzare le classi AliasLoader e Container di Laravel.
Ad esempio, la classe AliasLoader (dedicata alla gestione degli alias di classe) è una chiara applicazione di Singleton presente all’interno del core di Laravel. L’obiettivo di questo articolo non è descrivere il funzionamento di queste classi (anche perché puoi far riferimento all’ottimo articolo di Francesco per quello) ma è portarti a riflettere su quanto un pattern possa essere diffuso, e non solo nell’universo di PHP.
Se ancora non ti ho convinto, prova a investigare l’installazione del tuo framework in cerca di indizi, questo è quello che ho ottenuto da un mio progetto personale:
Il Singleton e il Concetto di Anti-Pattern
Come accennato inizialmente, il Singleton viene definito spesso un anti-pattern. Basta una semplice ricerca per trovare centinaia di pareri negativi sull’associazione di questo pattern al mondo del PHP. Dunque, è conveniente o no adoperare questo pattern? Come tutto in questo ambito, la risposta corretta è “dipende”. Dal mio personale punto di vista, posso dirti che non esiste un reale anti-pattern, ma esistono delle casistiche in cui l’applicazione di un pattern è preferibile rispetto ad altre.
Ritengo che il Singleton sia divenuto un anti-pattern più per motivi storici che per fatti concreti, registrando parecchi usi impropri del pattern che, in determinati contesti, veniva usato per qualsiasi tipologia di istanza. Ricordiamolo ancora una volta:
Il Singleton va usato esclusivamente quando in runtime si necessita di un’unica istanza di una classe, come ad esempio una classe per la gestione delle connessioni ad un db.
Nel Prossimo Episodio…
Inizieremo a sporcarci le mani con uno dei pattern creazionali fondamentali… l’Abstract Factory! Vedremo come è possibile migrare da una bad practice verso una modularizzazione delle nostre classi, focalizzando l’attenzione sul rispetto dei principi solid SRP (principio di singola responsabilità) e ISP (principio di segregazione delle interfacce).
Il Tuo Parere
Durante la stesura di questa serie, penso sia fondamentale capire quali siano le tue necessità, le tue idee e quali difficoltà trova la community nell’applicare quotidianamente questi concetti, quindi ogni feedback/dubbio è ben accetto!