Observer Design Pattern

L’observer è uno tra i più semplici ed intuitivi design pattern di tipo comportamentale. Questo tipo di pattern è utilizzato quando si è interessati allo stato di un oggetto e si desidera ricevere una notifica ogni qualvolta si verifichi un cambiamento di tale stato. Un caso pratico è quello di uno shop online in cui più utenti vogliono ricevere una notifica non appena un determinato item ritorni in stok e disponibile per l’acquisto.

Da un punto di vista formale, in accordo con la definizione data dalla Gang Of Four, lo scopo del  patter observer è quello di:

Definire una dipendenza uno-a-molti tra gli oggetti in modo che quando un oggetto cambia stato, tutti i suoi dipendenti vengono notificati e aggiornati automaticamente.

In altre parole il pattern specifica una comunicazione tra due tipologie di oggetti: gli osservatori, detti Observer, e gli osservabili, detti Subject. Un oggetto osservabile è quello che notificano agli osservatori i propri cambiamenti di stato. La struttura del pattern è mostrata nella figura seguente.

L’implementazione del pattern prevede che il Subject mantenga una lista di tutti gli Observer registrati. A tale scopo espone un metodo per la registrazione, register(), ed uno per la cancellazione, unregister(), degli degli osservatori. Questi ultimi, invece, espongono un metodo di aggiornamento, update(), che sarà invocato dall’oggetto osservato ad ogni cambiamento del proprio stato. Tale evento è rappresentato nell’implementazione proposta dal metodo notifyObservers(), per il quale proponiamo la seguente semplice implementazione:

Si noti che Java fornisce una piattaforma integrata per l’implementazione del pattern Observer tramite la classe java.util.Observable e l’interfaccia java.util.Observer. Tuttavia non è molto utilizzato perché l’implementazione del pattern è davvero molto semplice e spesso si preferisce non estendere la classe java.util.Observable fornita dal linguaggio solo per l’implementazione del pattern Observer, in quanto java non fornisce l’ereditarietà multipla.

Esempio Pratico

Riprendiamo il caso pratico che abbiamo introdotto all’inizio del post, e consideriamo la situazione in cui consumatori siano in attesa che un determinato item ritorni disponibile in stock. Implementiamo quindi due classi: un ItemProducer ed un ItemConsumer. I termini producer e consumer non sono stati scelti a caso, il pattern infatti è anche noto come modello publish-subscribe.

La classe ItemProducer, che implementa l’interfaccia Subject, è così definita:

Notiamo innanzitutto che la casse utilizza l’oggetto Item (osservato) per la sincronizzazione delle parti di codice che accedono alla lista degli observer. Inoltre si fa uso di una lista di appoggio per inserirvi tutti gli osservatori che si sono de-registrati. Questo al fine di evitare di avere una eccezione di ConcurrentModificationException durante l’esecuzione del metodo notifyObservers() per i motivi che vedremo tra breve. Ovviamente prima di poter eseguire le notifiche la lista degli observer è aggiornata rimuovendo gli osservatori de-registrati.

Aggiungiamo infine due metodi alla classe che sono specifici del dominio trattato. Il primo, addInStock(), che aggiunge nuovi item al magazzino e quindi invoca il metodo notifyObservers() per notificare ai consumatori che la quantità disponibile è stata aggiornata. Il secondo metodo, reserve(), che consente ai consumer di riservarsi il numero di item richiesti e che restituisce il numero di item effettivamente rilasciati. Questi infatti saranno in generale pari al numero di item richiesti, ad eccezione del caso in cui il magazzino ne contenga meno.

Veniamo ora all’implementazione della classe ItemConsumer, che estende l’interfaccia Observer:

La classe è istanziata sul numero di item richiesti, parametro required, e si registra come osservatore della classe producer che gli viene assegnato nel metodo addProducer(). Nel metodo update(), che ricordiamo è invocato dal producer per notificare un aggiornamento nello stock, si procede riservando item dal producer e, se sono sufficienti a coprire la richiesta, il consumer provvede a cancellare la propria registrazione come observer. E’ proprio tale cancellazione che potrebbe generare una ConcurrentModificationException, perchè rimuove l’observer dalla lista degli osservatori mentre il producer li sta ciclando per eseguire la notifica.

Per completezza riportiamo una classe Main che utilizza le due classi ed il relativo output generato.

Listener

Una ulteriore implementazione del pattern Observer è possibile utilizzando il concetto di Property Change Listener fornito dal linguaggio java ed implementato nel package java.beans. Il componente che gestisce la lista dei listener (ovvero degli observer) è il PropertyChangeSupport, che quindi deve essere istanziato nella classe ItemProducer ed utilizzato per inviare le notifiche:

La cosa più evidente di tale implementazione è che non sono più presenti porzioni di codice synchronized, in quanto non più necessari, e neppure la gestione della lista degli observer rimossi.

Per quanto riguarda invece l’implementazione dell’ItemConsume, non ci sono molte modifiche da apportare ad eccezione del fatto che la classe deve implementare l’interfaccia java.beans.PropertyChangeListener ed implementare il metodo propertyChange(), corrispondente al metodo update() del pattern.

Codice Sorgente

Il codice sorgente con l’esempio presentato è scaricabile qui observer.