Design Pattern in Java: Observer
In questo articolo vedremo come usare nei nostri programmi la classe Observable e l’interfaccia Observer, del package java.util, che implementano in Java il pattern Observer.
L’utilizzo del pattern Observer permette di risolvere elegantemente problemi che altrimenti richiederebbe qualche passaggio in più.
La classe Observable è utilizzata per creare delle sottoclassi osservabili da altre parti del programma: quando un oggetto Observable cambia, i suoi osservatori vengono avvisati immediatamente in modo quasi automatico. Le classi che osservano devono implementare l’interfaccia Observer e definire l’unico metodo dell’interfaccia, update. Questo metodo viene chiamato automaticamente quando l’oggetto osservato cambia. La sua firma è la seguente:
1 |
public void update(Observable oggettoOsservato, Object obj) |
Innanzitutto bisogna creare la classe osservata, sottoclasse di Observable, e una classe osservatrice che implementi l’interfaccia Observer. Successivamente, quando l’oggetto osservato cambia, questo segnala il fatto a tutti gli osservatori registrati chiamando setChanged (prima) e notifyObservers (dopo). La firma di questi metodi è la seguente:
1 2 3 |
public void setChanged() public void notifyObservers() public void notifyObservers(Object obj) |
setChanged indica che l’Observable è stato modificato, mentre notifyObservers lo segnala agli osservatori.
Attenzione! se si chiama notifyObservers prima di setChanged, gli osservatori non riceveranno alcuna notifica!!
Da notare che notifyObservers è presente in due versioni: una con argomento, l’altra senza. Se si chiama notifyObservers con un argomento, questo verrà passato ad update, altrimenti ad update sarà passato il valore null. Questo argomento, essendo un Object, può essere un qualsiasi oggetto ed è molto utile per condividere dati tra gli oggetti.
Per rendere tutto funzionante, occorre registrare l’osservatore sull’oggetto osservato, usando il metodo addObserver. La sua firma, insieme a quella degli altri metodi di Observable, è qui sotto:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
void addObserver(Observable obs) // obs diventa osservatore dell'oggetto che lo chiama questo metodo void deleteObserver(Observable obs) // rimuove l'osservatore obs dall'oggetto osservato chiamante void deleteObservers() // rimuove tutti gli osservatori registrati sull'oggetto di invocazione protected void clearChanged() // annulla la segnalazione di cambiamento (ma non la notifica, se e' gia' stata avviata!!) int countObservers() // restituisce il numero di osservatori registrati sull'oggetto chiamante boolean hasChanged() // restituisce true se l'oggetto e' cambiato ed e' pronto alla notifica |
Per chiarire un pò le idee, qui sotto ho preparato un semplice esempio:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
import java.util.Observable; import java.util.Observer; public class Esempio{ public static void main(String[] args){ // istanzio l'oggetto osservatore e l'oggetto da osservare Osservatore osservatore = new Osservatore(); Osservato osservato = new Osservato(); // aggiungo all'oggetto da osservare l'osservatore osservato.addObserver(osservatore); // faccio partire il conto alla rovescia osservato.contoAllaRovescia(10); } } class Osservato extends Observable { public void contoAllaRovescia(int n) { for ( ; n >= 0; n--) { // l'oggetto e' cambiato setChanged(); // notifico il cambiamento all'osservatore notifyObservers(new Integer(n)); // stoppo il thread corrente // (solo per esigenze di visualizzazione) try { Thread.sleep(200); } catch (InterruptedException ex){ System.err.println("thread stoppato: " + ex); } } } } class Osservatore implements Observer { public void update(Observable oggettoOsservato, Object obj) { // ottengo il valore di n passato da // notifyObservers ad update int n = ((Integer)obj).intValue(); System.out.println("" + n); } } |
Questo programma è formato da un oggetto Observable che avvia un conto alla rovescia a partire da un valore specificato. Ad ogni modifica del conteggio, viene notificato il cambiamento all’unico osservatore. L’osservatore si occupa di stampare a schermo il valore ricevuto. Tra una notifica e la successiva il thread viene stoppato per 0.2 secondi solo per esigenze di visualizzazione.
Non c’è limite al numero di osservatori registrabili su uno stesso oggetto. Quest’altro esempio è una versione modificata dell’esempio precedente, con l’aggiunta di un secondo osservatore:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
import java.util.Observable; import java.util.Observer; public class Esempio2 { public static void main(String[] args) { // istanzio gli oggetti osservatori // e l'oggetto da osservare Osservatore1 osservatore1 = new Osservatore1(); Osservatore2 osservatore2 = new Osservatore2(); Osservato osservato = new Osservato(); // aggiungo all'oggetto da osservare gli osservatori osservato.addObserver(osservatore1); osservato.addObserver(osservatore2); // faccio partire il conto alla rovescia osservato.contoAllaRovescia(10); } } class Osservato extends Observable { public void contoAllaRovescia(int n) { for ( ; n >= 0; n--) { // l'oggetto e' cambiato setChanged(); // notifica il cambiamento agli osservatori notifyObservers(new Integer(n)); // stoppa il thread corrente // (solo per esigenze di visualizzazione) try { Thread.sleep(200); } catch (InterruptedException ex) { System.err.println("thread stoppato: " + ex); } } } } // primo osservatore class Osservatore1 implements Observer { public void update(Observable oggettoOsservato, Object obj) { // ottengo il valore di n passato // da notifyObservers ad update int n = ((Integer)obj).intValue(); // stampa fino a 0 escluso if (n > 0) { System.out.println("Osservatore 1:tt" + n); } } } // secondo osservatore class Osservatore2 implements Observer { public void update(Observable oggettoOsservato, Object obj) { // ottengo il valore di n passato // da notifyObservers ad update int n = ((Integer)obj).intValue(); if (n == 0) { System.out.println("Osservatore 2:ttFINE"); } } } |
Il primo osservatore si comporta esattamente come nel caso precedente. Il secondo osservatore invece attende che il conto alla rovescia arrivi a zero per stampare a schermo la parola “FINE”.
Conclusioni
Observable ed Observer non sono aspetti fondamentali del linguaggio ma in certe occasioni possono tornare particolarmente utili, specialmente in applicazioni multithread. Saper usare bene queste due classi ci permetterà di risolvere in maniera semplice e raffinata problemi che altrimenti richiederebbero un impegno maggiore (ed un codice più confuso 😛 ).