Observer

Tale pattern è noto con diversi nomi:

  • publish-subscribe
  • delegation event model
  • observer-observable

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:

  • soluzione di tipo pull: quando il totale cambia la GUI chiede (tira verso di sé) alla Sale (direttamente o tramite controller) il totale aggiornato, per visualizzarlo. Il problema è che la GUI dovrebbe sapere quando il totale cambia, e questo richiederebbe di duplicare una parte significativa della logica applicativa anche nello strato della UI.
  • Soluzione di tipo polling: la GUI chiede periodicamente alla Sale se il suo totale è cambiato e in tal caso lo aggiorna e lo visualizza. Le soluzioni basate sul polling sono in genere poco efficienti.
  • Una terza soluzione di tipo push è che la Sale quando cambia il totale, invia (push) il dato aggiornato alla GUI, che si comporterà di conseguenza e lo aggiornerà.
Il principio di separazione Modello-Vista

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.

NomeObserver (publish-subscribe)
ProblemaDiversi 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?
SoluzioneDefinire 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.

Pasted image 20230627111921.png
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.

Esempio in Java
...

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.

L'interfaccia PropertyListener
...

Pasted image 20230627113003.png

La classe Sale
...

Pasted image 20230627113050.png
Abbiamo costruito una classe Sale che ha il codice della Sale corrente, il totale di tale Sale e un ArrayList di property listener.
Pasted image 20230627113131.png
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).

La classe per la GUI
...

Pasted image 20230627113755.png
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.

Il main
...

Pasted image 20230627114017.png
Con uno scanner vengono presi in input i prezzi dei nuovi prodotti scansionati.

L'esecuzione
...

Pasted image 20230627114102.png
Si noti come, in automatico, vengono aggiornati i valori.

Tornando all'esempio mostrato nei diagrammi sopra
...

Da un punto di vista dei DSD, nella fase di inizializzazione abbiamo:
Pasted image 20230627114232.png

Quando il totale cambia, Sale itera su tutti i suoi iscritti e chiama il metodo per comunicare l'aggiornamento avvenuto in sé stessa:
Pasted image 20230627114340.png

La finestra, che ha implementato tale metodo come preferiva effettua la sua operazione, in corrispondenza dell'aggiornamento:
Pasted image 20230627114425.png

Observer non servo solo a connettere le UI e gli oggetti del modello
...

Come ulteriore esempio si consideri, la seguente figura:
Pasted image 20230627114636.png
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.

Sul pattern Observer

Più in generale in un'applicazione ci possono essere:

  1. diversi tipi di eventi e, in corrispondenza, diverse interfacce listener;
  2. per ciasun tipo di eventi ci possono essere diversi publisher;
  3. ogni publisher può essere l'editore di più tipi di eventi;
  4. ogni publisher può avere zero o più subscriber;
  5. ogni subscriber può essere abbonato a zero o più publisher, anche con riferimento a diversi tipi di eventi;
  6. le relazioni tra publisher e subscriber possono cambiare dinamicamente.