L'observer pattern è una particolare tipologia di design pattern, cioè uno schema progettuale o modello in grado di fornire soluzioni per la risoluzione di problematiche che si presentano frequentemente; nell'ambito del paradigma Object-oriented, i design pattern svolgono la loro funzione mettendo a disposizione una rappresentazione delle relazioni e delle dinamiche d'interazione fra classi o oggetti.
Nel caso specifico, l'observer pattern viene adottato per monitorare lo stato di oggetti differenti; in pratica, dati uno o più oggetti, essi verranno registrati in modo da gestire un evento che potrebbe essere prodotto da un ulteriore oggetto, cioè quello posto sotto osservazione, come per esempio un suo cambiamento di stato.
Utilizzabile anche nello sviluppo in PHP, tale schema può aver delle interessanti applicazioni pratiche delle quali verrà fornito un approfondimento nel corso di questa trattazione.
Struttura di un observer pattern
Tecnicamente, un observer pattern permette di estendere o di modificare il comportamento di un classe senza la necessità che essa subisca dei cambiamenti; nello specifico, quando un metodo riceve una chiamata attraverso un oggetto, quest'ultimo segnala l'evento ad altri oggetti che sono in attesa di una notifica riguardante il suo stato.
L'oggetto coinvolto dall'evento prende il nome di subject, mentre quelli destinati a riceverne la notifica sono denominati observers o listeners, la comunicazione tra il primo e i secondi è possibile grazie ad un terzo componente chiamato dispatcher, anche quest'ultimo è definibile come un oggetto e funge essenzialmente come gestore di connessioni.
In tale meccanismo interviene un quarto elemento che funge da agente per l'evento che coinvolge il subject, esso potrebbe essere per esempio un utente che esegue una determinata azione o un semplice processo.
Per via delle peculiarità esposte, nell'economia degli schemi di progettazione gli observer pattern appartengono alla categoria dei cosiddetti pattern comportamentali (behavioral patterns) che riguardano il modo in cui le classi interagiscono con gli oggetti e gli oggetti entrano in relazione tra di loro all'interno di una dinamica di callback (richiamo), in essa uno o più blocchi di codice vengono appunto richiamati da un altro.
In pratica, un observer pattern definisce le dipendenze tra oggetti differenti in modo tale che, quando un oggetto vede il suo stato modificato, le relative dipendenze vengano automaticamente aggiornate.
Implementazione standard degli observer pattern in PHP
In PHP 5.x è disponibile una modalità d'implementazione per gli observer pattern attraverso le librerie SPL (Standard PHP Library), una raccolta di interfacce per l'accesso ai dati e di classi per la risoluzione di problematiche ricorrenti.
Nello specifico entrano in gioco due interfacce che potranno essere utilizzate attraverso delle classi destinate ad implementarle realizzandone i metodi: splSubject e splObserver; alla prima fanno riferimento tre metodi:
SplSubject::attach
: accetta e attribuisce un observer memorizzandolo in un oggetto, in pratica associa degli observers ad un subject;SplSubject::detach
: libera l'oggetto in cui l'observer è archiviato da quest'ultimo, in pratica rimuove l'associazione tra subject ed observers;SplSubject::notify
: effettua una notifica a tutti gli observers attribuiti relativamente ad un evento che riguarda il subject.
splObserver è invece correlato ad un solo metodo, denominato SplObserver::update
, la cui funzione è quella di ricevere un aggiornamento dal subject, esso verrà chiamato ogni volta che si verificherà un evento atteso.
Lo schema funzionale implementato dalle SPL per tale pattern è semplice e lineare:
- l'agente determina un'azione a carico del subject determinandone una modifica di stato;
- il dispatcher effettua una connessione agli observers;
- gli observers intercettano la comunicazione transitata attraverso il dispatcher e registrano l'evento riguardante il subject.
Un esempio di "traduzione in codice" dello schema presentato potrebbe essere quello in cui si crea un'applicazione che simula un caso concreto, cioè un potenziale debitore (il subject) che effettua una richiesta a due possibili creditori (gli observers) e riceverà risposte differenti a seconda dell'entità di quest'ultima.
Il primo passaggio sarà quello relativo alla definizione degli observers:
/**
la classe Observer implementa l’interfaccia SplObserver
e introduce i due metodi che verranno invocati al momento
della registrazione di una modifica di stato del subject
*/
class Observer implements SplObserver {
protected $papero;
public function __construct($papero) {
$this->papero = $papero;
}
# il nome della classe viene concatenato al costruttore
public function __toString() {
return "(".__CLASS__.") ".$this->papero;
}
# il metodo update() introduce i metodi richiamati
# in seguito all’evento in cui è coinvolto il subject
public function update(SplSubject $subject) {
if ($subject->menoDiMille() == true) {
echo $this . ": - Te li presto. <br />";
}
if ($subject->piuDiMille() == true) {
echo $this . ": - Ma in due rate. <br />";
}
}
}
# il secondo observer viene definito all’interno di una
# sottoclasse che estende la classe del primo observer
class ObserverB extends Observer {
public function update(SplSubject $sub) {
if ($sub->piuDiMille() == true) {
echo $this . ": - Non ti do un centesimo. <br />";
}
}
}
La classe Observer
implementa l'interfaccia splObserver
e introduce un costruttore la cui funzione sarà quella di rendere disponibile l'oggetto creato dall'istanza della classe.
Essa integra poi il metodo magico __toString()
che consente alla classe di definire quali saranno i suoi comportamenti nel caso in cui essa venga manipolata come una stringa; in questo modo si avrà a disposizione l'informazione relativa al nome della classe stessa.
Viene quindi richiamato il metodo update()
, esso introduce due ulteriori metodi destinati a produrre risposte differenti a seconda del tipo di richiesta proveniente dal subject.
La derivata ObserverB
estende la classe principale per ereditarietà, anch'essa contiene un'implementazione di update()
che richiama un metodo della principale e genera un'altra possibile risposta all'evento riguardante il subject.
La classe successiva implementa invece l'interfaccia SplObserver
e tutti i metodi di SplSubject
. La vedremo nella prossima pagina.
Questa classe implementa invece l'interfaccia SplObserver
e tutti i metodi di SplSubject
/*
la classe Subject implementa l’interfaccia SplSubject
*/
class Subject implements SplSubject {
private $papero;
private $observers = array();
private $menodimille = false;
private $piudimille = false;
public function __construct($papero) {
$this->papero = $papero;
}
public function __toString() {
return "(".__CLASS__.") ".$this->papero;
}
# il metodo attach() attribuisce gli observers al subject
public function attach(SplObserver $obs) {
$this->observers[] = $obs;
}
# il metodo detach() dissocia gli observers al subject
public function detach(SplObserver $obs) {
$this->observers = array_diff($this->observers, array($obs));
}
# notify() segnala il verificarsi di un evento a carico del subject
public function notify() {
foreach($this->observers as $observer) {
$observer->update($this);
}
}
# definizione dei valori di ritorno
public function chiedePiuDiMille($val) {
$this->piudimille = $val;
if ($val == true) {
echo $this . ": - Mi servono 1.200 Dollari. <br />";
}
$this->notify();
}
public function piuDiMille() {
return $this->piudimille;
}
public function chiedeMenoDiMille($val) {
$this->menodimille = $val;
if ($val == true) {
echo $this . ": - Mi servono 500 Dollari. <br />";
}
$this->notify();
}
public function menoDiMille() {
return $this->menodimille;
}
}
Come è possibile notare dal codice proposto, il metodo attach()
memorizza gli observers in un oggetto generando un array che successivamente verrà "ciclato" in modo che ciascun observer possa ricevere un eventuale aggiornamento sullo stato del subject attraverso il metodo update()
; il metodo detach()
si occupa di eliminare l'associazione tra subject ed observers precedentemente attribuita, ciò avviene attraverso un confronto basato sulla funzione array_diff()
tra i valori presenti nei vettori contenenti gli observers.
notify()
entra in gioco ogni volta che viene prodotto un evento che coinvolge il subject, naturalmente le modifiche di stato dell'oggetto monitorato non dovranno essere necessariamente registrate da entrambi gli observers, il detach a carico di uno di essi escluderà infatti quest'ultimo dalla registrazione dell'evento.
Il passaggio finale consiste nell'istanza delle classi e nell'invocazione dei relativi metodi:
/*
Istanza delle classi e generazione degli oggetti
associati a subject ed observers
*/
$observer = new Observer("Paperoga");
$observerB = new ObserverB("Paperone");
$subject = new Subject("Paperino");
# invocazione dei metodi
$subject->attach($observer);
$subject->attach($observerB);
$subject->chiedeMenoDiMille(false);
$subject->chiedeMenoDiMille(true);
$subject->chiedePiuDiMille(true);
$subject->detach($observerB);
$subject->chiedePiuDiMille(true);
L'esecuzione dell'applicazione porta alla generazione del seguente output:
(Subject) Paperino: - Mi servono 500 Dollari.
(Observer) Paperoga: - Te li presto.
(Subject) Paperino: - Mi servono 1.200 Dollari.
(Observer) Paperoga: - Te li presto.
(Observer) Paperoga: - Ma in due rate.
(Observer) Paperone: - Non ti do un centesimo.
(Subject) Paperino: - Mi servono 1.200 Dollari.
(Observer) Paperoga: - Te li presto.
(Observer) Paperoga: - Ma in due rate.
L'istanza delle tre classi disponibili porta all'attribuzione di un nome al subject e ad entrambi gli observers, segue quindi una doppia invocazione del metodo attach()
per l'associazione del subject ai due oggetti incaricati del monitoraggio.
Fatto questo, il subject subisce un primo cambiamento di stato, dovuto alla chiamata del metodo chiedeMenoDiMille()
che viene registrato soltanto dal primo observer; la successiva modifica di stato, causata dalla chiamata del metodo chiedePiuDiMille()
, è invece registrata da entrambi gli observers dando luogo a tutti i comportamenti previsti.
Infine, viene nuovamente invocato il metodo richiamato in precedenza, ma dato che il secondo observer è stato liberato tramite un detach()
applicato all'oggetto generato dall'istanza della sottoclasse, questa volta soltanto l'observer relativo alla classe principale può registrare la modifica di stato del subject.
Conclusioni
L'observer pattern è un particolare tipo di design pattern basato su uno o più oggetti che vengono definiti con lo scopo di effettuare la gestione di un potenziale evento generato da un oggetto posto sotto monitoraggio.
I suoi ambiti d'utilizzo sono numerosi, in questa trattazione è stato proposto il codice di un'applicazione in grado di fornire risposte differenti alle modifiche di stato di un oggetto, ma gli observer pattern potranno essere utilizzati anche per altri scopi, come per esempio notificare gli effetti di un tentativo di autenticazione in uno script per il login o per definire output differenti a seconda delle eccezioni registrate durante l'esecuzione di un'applicazione, tutto dipende dalle esigenze delle sviluppatore.