Tale pattern è noto con diversi nomi:
In questa trattazione sarà spiegato con i nomi publish-subscribe, poiché sembra più chiaro da comprendere.
Un requisito nello sviluppo delle applicazioni che abbiamo usato come esempi fino ad ora è quello di aggiungere una interfaccia grafica e aggiornare la visualizzazione in base ai cambiamenti che avvengono nel modello di dominio.
Per esempio nel caso del POS, vogliamo che ogni qualvolta viene scansionato un nuovo prodotto, il totale venga aggiornato anche nella finestra che emette graficamente l'interfaccia utente.
Possiamo immaginare diverse soluzioni:
Il principio di separazione Modello-Vista utilizzato in MVC, sembra scoraggiare la terza soluzione. Tale principio afferma che gli oggetti modello (oggetti non della UI, come la Sale) non devono conoscere direttamente gli oggetti vista (come una finestra). Queste motivazioni sono mosse da Low Coupling, ovvero si vuole un accoppiamento basso tra modello e vista. Infatti, se per esempio si volesse aggiornare la vista, grazie ad un accoppiamento basso non ci saranno ripercussioni sullo strato di dominio. Per questo il principio Modello-Vista supporta anche Protected Variations. Tuttavia, questo principio non proibisce una conoscenza o collaborazione indiretta dagli oggetti del modello a quelli della vista. Questa è una delle idee alla base del pattern di cui stiamo discutendo.
Nome | Observer (publish-subscribe) |
---|---|
Problema | Diversi tipi di oggetti subscriber (abbonato) sono interessati ai cambiamenti di stato o agli eventi di un oggetto publisher (editore). Ciascun subscriber vuole reagire a modo proprio quando il publisher invia degli aggiornamenti. Inoltre il publisher vuole mantenere un accoppiamento basso con i subscriber. Che cosa fare? |
Soluzione | Definire un'interfaccia subscriber. Gli oggetti subscriber implementano questa interfaccia. Il publisher ha una lista di subscriber che sono interessati ai suoi eventi, e li avvisa quando qualcosa in sé cambia. |
Si legga la figura partendo dalla classe Sale.
La classe Sale ha come membro una lista (una Array per esempio) di PropertyListener (ascoltatore di proprietà).
Il metodo addPropertyListener(PropertyListener lis)
consente di aggiungere un property listener alla sua lista.
Quando Sale chiama il metodo setTotal(Money newTotale)
che cambia il totale, chiama anche publishPropertyEvent()
dove aggiorna ogni listener, scorrendo la lista. Per aggiornarli, invia sé stesso, il nome della proprietà che è cambiato e il nuovo valore usando il metodo onPropertyEvent(source, name, vaue)
.
SaleFrame1, è la finestra che usa SWING. La finestra implementa l'interfaccia PropertyListener.
Quando la finestra viene inizializzata le viene passata una Sale.
Per ricevere gli aggiornamenti implementa il metodo dell'interfaccia onPropertyEvent(source, name, value)
come preferisce. Poi aggiunge sé stesso alla lista degli abbonati di Sale. Per questo, quando Sale, scorrendo gli abbonati, invia gli aggiornamenti chiama sugli abbonati (la finestra SWING fra questi) il metodo onPropertyEvent
.
Per questo esempio non utilizzeremo una interfaccia grafica vera e propria, ma solo delle stampe a video, il principio è comunque valido e applicabile a interfacce grafiche.
PropertyListener
Abbiamo costruito una classe Sale che ha il codice della Sale corrente, il totale di tale Sale e un ArrayList di property listener.
Qui invece abbiamo il metodo per incrementare il totale della vendita (setTotal).
Alcuni metodi per ottenere il totale e il codice della Sale.
Il metodo per aggiungere alla lista gli oggetti che implementano PropertyListener.
Si noti che quando viene effettuata una modifica al totale, viene chiamato il metodo publisherPropertyEvent dove vengono scorsi tutti gli abbonati, e su ognuno di essi viene chiamato il metodo dell'interfaccia (che ognuno ha implementato a proprio modo).
Quando questa classe viene creata (e quindi nel costruttore) deve essere aggiunta questa classe stessa alla lista degli ascoltatori della Sale. La Sale viene passata nel costruttore stesso.
A questo punto, ogni volta che la Sale aggiorna il totale, chiamata il metodo onPropertyEvent di tutti i suoi abbonati (questa classe compresa) che aggiorna l'interfaccia grafica, nel nostro caso stampa il nuovo valore del totale.
Con uno scanner vengono presi in input i prezzi dei nuovi prodotti scansionati.
Si noti come, in automatico, vengono aggiornati i valori.
Da un punto di vista dei DSD, nella fase di inizializzazione abbiamo:
Quando il totale cambia, Sale itera su tutti i suoi iscritti e chiama il metodo per comunicare l'aggiornamento avvenuto in sé stessa:
La finestra, che ha implementato tale metodo come preferiva effettua la sua operazione, in corrispondenza dell'aggiornamento:
Come ulteriore esempio si consideri, la seguente figura:
AlarmClock pubblica eventi e diversi subscriber.
Questo esempio sottolinea il fatto che molte classi possono implementare l'interfaccia AlarmListener, molti oggetti possono essere registrati contemporaneamente come listener, e tutti possono reagire ad un evento "allarme" in un proprio modo unico.
Più in generale in un'applicazione ci possono essere: