Sei sulla pagina 1di 10

Sul pattern Observer

1 Introduzione
Consideriamo la situazione in cui lo stato di uno o pi` u oggetti (Observers) debba essere congruente con quello
di un dato oggetto (Subject). Si pu`o stabilire un meccanismo per cui questultimo informa gli altri oggetti tutte
le volte che il suo stato subisce una variazione, in modo che essi possano prendere gli opportuni provvedimenti;
in particolare un osservatore pu`o richiedere il nuovo stato del soggetto e aggiornare coerentemente il proprio.
Il paradigma corrisponde al modello Publisher and Subscribe: i sottoscrittori si registrano presso un pub-
blicatore e questultimo li informa ogni volta che ci sono nuove notizie (del genere sottoscritto). Figura 1 `e una
schematizzazione del pattern dal punto di vista operativo.
Soggetto
Osservatore 1
Osservatore 2
Osservatore 3
Subject
Observable
Publisher
Observers
Subscrivers
Figura 1: Il pattern observer in azione.
Il diagramma UML del design pattern Observer `e in Figura 2. Soggetto `e una classe astratta da cui eredita
il soggetto concreto, mentre Osservatore `e uniterfaccia implementata dagli osservatori concreti. Soggetto
tiene i riferimenti agli osservatori per poterli avvisare, mentre gli osservatori concreti tengono il riferimento al
soggetto per poterne leggere lo stato.
Figura 2: Il pattern observer.
Pi` u specicatamente Soggetto deve prevedere i seguenti metodi:
registraOsservatore() chiamato da un osservatore che intende registarsi;
rimuoviOsservatore() chiamato da un osservatore che intende deregistrarsi.
Inoltre il soggetto deve prevedere un modo per informare gli osservatori di un avvenuto cambiamento di stato.
Nel seguito ipotizziamo che a tal ne Soggetto esegua il proprio metodo informaOsservatori(). Questo
metodo chiama il metodo aggiorna() presentato da tutti gli osservatori. Il metodo aggiorna() a sua volta
chiama il metodo getStato() del soggetto concreto per ottenere il suo nuovo stato.
1
In Figura 3 viene data una illustrazione dettagliata del pattern Observer, mostrando tutti i metodi. Notare
che linterfaccia di Soggetto presenta il metodo setStato(), responsabile di aggiornare lo stato del soggetto.
In Figura 3 `e stata prevista la classe Stimolatore che provvede a chiamare setStato().
Figura 3: Il pattern observer. Dettagli.
In Figura 4 viene mostrato il diagramma di sequenza relativo al cambiamento di stato del soggetto.
Figura 4: La sequenza innescata dal cambiamento di stato di Soggetto a causa dello stimolo ricevuto.
2
Denizione
Il pattern Observer denisce una relazione uno a molti tra oggetti tale che se un oggetto cambia il
suo stato gli oggetti da esso dipendenti vengono informati e aggiornati automaticamente.
2 Commenti
Il pattern costituisce un ottimo esempio di applicazione del seguente principio:
Fare in modo che laccoppiamento tra oggetti sia il pi` u lasco (loosely) possibile.
Con riferimento allo schema di Figura 3 e 4 si possono fare le seguenti osservazioni.
1. Nel nostro modello cambiamento di stato del soggetto avviene esclusivamente per invocazione del metodo
setStato(). Pi` u in generale, un soggetto pu`o essere deputato al rilevamento di un qualunque genere di
evento (che si rivela con meccanismi diversi dalla chiamata di un metodo).
2. Il nostro osservatore, quando riceve la notica, richiede al soggetto (concreto) il suo stato attraverso
loperazione getStato().
`
E perfettamente legittimo assumere che loperazione aggiorna() preveda il
passaggio di un parametro (lo stato del soggetto) allosservatore, eliminando la necessit`a per losservatore
di eettuare questa lettura.
1
3. Un osservatore potrebbe essere interessato a pi` u tipi di eventi, rilevati da soggetti diversi (sui quali,
ovviamente, si dovr`a registrare). In questo caso il metodo aggiorna() dovr`a prevedere un ulteriore
parametro: lidenticatre del (il riferimento al) soggetto che sta noticando levento.
4. Nel Soggetto di Figura 3, la variabile stato viene letta e scritta attraverso i metodi getStato() e
setStato() rispettivamente. Ovviamente questa non `e una soluzione obbligatoria.
`
E perfettamente
lecito non prevedere in Soggetto ne la componente stato ne i due metodi menzionati, rinviando la loro
denizione a SoggettoConcreto. In tal caso in Soggetto resterebbero soltanto le operazioni relative alla
gestione degli osservatori.
Con riferimento ai due metodi getStato() e setStato(), vogliamo allargare la discussione al loro uso ge-
neralizzato nello sviluppo di applicazioni complesse.
Essi esemplicano due categorie di metodi caratteristici di classi che rappresentano concetti del dominio ap-
plicativo: metodi getter e setter. Le classi del dominio applicativo vengono indicate come classi entity, esse
hanno la funzione di contenere dati, ovvero valori (ad esempio, ContoCorrente, Fattura, Impiegato,..). Per tale
motivo gli oggetti istanziati da queste classi sono detti value objects, ovvero oggetti che hanno al loro interno
un valore. Il modello a oggetti dellapplicazione `e spesso un insieme di value objects ai quali si aggiungono
oggetti (con funzione di controllo o di manager) che implementano la logica dellapplicazione. I value objects
presentano metodi getter e setter per prelevare/modicare i valori custoditi al loro interno.
3 Realizzazione
Viene ora mostrata la realizzazione del modello di Figura 3.
Cominciamo dallinterfaccia Osservatore. Essa `e dichiarata semplicemente in questo modo:
public interface Osservatore {
public void aggiorna();
}
Per quanto riguarda il Soggetto, assumiamo senzaltro che la variabile stato sia intera. Soggetto ha
il membro osservatori per tener traccia degli osservatori. Dichiariamo osservatori come un ArrayList,
una struttura che implementa come vettore una lista di oggetti (si veda la documentazione Java). Su tale
struttura sono denite svariate operazioni, tra cui: add(E e) che aggiunge un elemento in fondo alla lista,
indexOf(Object o) che cerca nella lista la prima occorrenza dellelemento o, remove(int i) che elimina un
elemento a alla posizione specicata, size() che restituisce il numero di elementi in lista.
Le tre operazioni registraOsservatore(), rimuoviOsservatore() e informaOsservatori() assumono la
forma sotto riportata. Il signicato di getStato() e setStato() `e stato discusso in precedenza.
1 `
E questa la soluzione adottata in Java.util e in altre API Java.
3
public abstract class Soggetto {
protected int stato;
protected ArrayList osservatori;
public void registraOsservatore(Osservatore o) {
osservatori.add(o);
}
public void rimuoviOsservatore(Osservatore o) {
int i = osservatori.indexOf(o);
if (i>=0) osservatori.remove(i);
}
public void informaOsservatori() {
for (int i=0; i<osservatori.size(); i++){
Osservatore o = (Osservatore) osservatori.get(i);
o.aggiorna();
}
public abstract void setStato(int s);
public abstract int getStato();
}
Il costruttore di un soggetto concreto deve istanziare la lista di osservatori oltre a implementare i due metodi
getStato() e setStato().
public class SoggettoConcreto extends Soggetto{
public SoggettoConcreto(){
osservatori = new ArrayList();
stato = 0; // stato iniziale
}
public void setStato(int s) {
stato= s;
informaOsservatori();
}
public int getStato() {
return stato;
}
}
Losservatore concreto viene implementato prevedendo per esso anche lidenticativo Id (una stringa) che ser-
va a riconoscerlo. Il costruttore viene chiamato passando lidenticatore e il riferimento al soggetto (ovviamente
viene passato un soggetto concreto, ma si usa il polimorsmo dichiarando il secondo parametro come Soggetto).
Notare che il riferimento alloggetto osservato viene passato una volta per tutte tramite il costruttore. In altre
parole, il legame `e statico e questo osservatore osserva sempre il solito soggetto.
public class OsservatoreConcreto implements Osservatore{
private String Id;
private int stato; //stato dellosservatore concreto
private Soggetto soggetto;
public OsservatoreConcreto(String Id, Soggetto soggetto){
this.Id= Id;
this.soggetto= soggetto; //il soggetto osservato
stato = Id.length(); //Per dare uno stato iniziale
}
public void aggiorna() {
stato = stato + soggetto.getStato();
}
}
4
La parte che segue esemplica come uno stimolatore esercita il pattern.
::::::
Soggetto soggetto = new SoggettoConcreto();
Osservatore o1 = new OsservatoreConcreto("Pippo", soggetto);
soggetto.registraOsservatore(o1);
OsservatoreConcreto o2 =new OsservatoreConcreto("Paperino", soggetto);
soggetto.registraOsservatore(o2);
soggetto.setStato(i); // i `e un numero intero
::::::
soggetto.rimuoviOsservatore(o2);
4 Il pattern Observer col supporto di Java
Sia il package java.util sia lAPI Swing forniscono supporto allimplementazione del pattern Observer.
4.1 Il pattern Observer di Java.util
Il package contiene la classe Observable e linterfaccia Observer, corrispondenti ai nostri Soggetto) e Osser-
vatore, ma con qualche funzionalit`a in pi` u.
Linterfaccia Observer presenta il solo metodo: update(Observable o, Object arg), dove il primo para-
metro `e il riferimento al soggetto che sta chiamando il metodo, mentre il secondo `e un parametro (della classe
Oggetto!) che il soggetto passa allosservatore (Cfr. punti 2 e 3 del paragrafo 2).
La classe Observable mostra queste operazioni (il rapporto con le operazioni previste per il nostro Soggetto
`e evidente)
addObserver(Observer o) //aggiunge losservatore o
deleteObserver(Observer o) //rimuove losservatore o
notifyObservers() //informa gli osservatori
Nellimpiego di Observable occorre considerare anche loperazione setChanged() la quale ha leetto di por-
tare loggetto in uno stato di cambiamento di stato avvenuto. A dierenza del nostro soggetto, Observable
informa gli osservatori solo se esso si trova in questo stato. E questa una caratteristica del supporto Java che
conferisce maggiore essibilit`a al programmatore. Ad esempio, `e possibile che un soggetto voglia avvisare i
suoi osservatori solo quando un cambiamento delle sue variabili interne soddis a una certa condizione. In tal
caso, gli eventuali metodi che modicano le variabili non determineranno automaticamente la segnalazione agli
osservatori, ma lo faranno solo quando determineranno un cambiamento di stato che soddisfa quella condizione.
In pratica un Observable ha una variabile interna booleana, diciamo, changed, che viene asserita con lesecuzio-
ne di setChanged(). Il metodo notifyObservers() informa gli osservatori solo se questa variabile `e asserita e
dopo aver informato la disasserisce. In questo modo `e necessario che venga eseguito nuovamente setChanged()
perche possa esserci unaltra notica.
Si noti che la forma normale del metodo di notica `e notifyObservers(Object arg), la forma notifyObservers()
equivale a notifyObservers(null). Lesecuzione del metodo notifyObservers(Object arg) determina la
chiamata del metodo update(Observable o, Object arg) dellosservatore, dove il primo parametro `e il rife-
rimento a se stesso (il soggetto).
4.1.1 Prima versione
Questa prima versione rispecchia quella del Paragrafo 3.
Viene denito un SoggettoConcreto che estende Observable e OsservatoreConcreto che implementa
Observer. Viene fatto ricorso a notifyObservers() senza parametri, continuando a servirsi di getStato()
per ottenere lo stato del soggetto. Per quanto si riferisce al soggetto si ha:
5
public class SoggettoConcreto extends Observable{
int stato;
SoggettoConcreto(){
stato = 0;
}
public void setStato(int s) {
stato= s;
this.setChanged(); //dice che lo stato `e cambiato
this.notifyObservers(); //notifica agli osservatori
}
public int getStato() { //come prima
return stato;
}
}
Per quanto si riferisce allosservatore, poiche il metodo update() passa il soggetto, il costruttore dellosservatore
non prevede pi` u il passaggio del soggetto
2
public class OsservatoreConcreto implements Observer{
private String Id;
private int stato;
OsservatoreConcreto(String Id){
this.Id= Id;
stato = Id.length();
}
public void update(Observable o, Object arg) { //non ci si cura di arg
if (o instanceof SoggettoConcreto){ //questo `e il soggetto
stato= stato+((SoggettoConcreto)o).getStato();
}
}
public String getId(){
return Id;
}
}
Ovviamente listanziazione dellosservatore XXXX da parte del programma client prende questa forma:
OsservatoreConcreto o = new OsservatoreConcreto(XXXX );
4.1.2 Seconda versione
Rispetto alla precedente versione viene impiegata la forma update(Object arg). Il parametro arg viene usato
per passare lo stato del soggetto. Ci`o rende inutile il metodo getStato() nel soggetto, ma comporta una
variazione nel modo di tenere lo stato, in quanto il parametro arg `e un Object (non un tipo primitivo) e non si
pu`o fare nel testo di chi riceve loggetto una conversione da Object a int.
La soluzione adottata consiste nel denire la classe stato avente una componente intera che corrisponde allo
stato precedente.
3
La classe ha un metodo get e un set.
2
Si ricordi che `e il programma client (lo Stimolatore) a registrare losservatore presso il soggetto e quindi, sar`a questo che dir`a
la sua identit`a allosservatore tramite il metodo update().
3
Chi `e capace di trovare una soluzione pi` u elegante?
6
public class Stato {
private int i;
Stato(){
i=0;
}
public void setStato(int i){
this.i = i;
}
public int getStato(){
return i;
}
Il soggetto prende questa forma
public class SoggettoConcreto extends Observable {
private Stato stato; //lo stato del soggetto
public SoggettoConcreto(){
stato = new Stato(); //stato iniziale = 0
}
public void setStato(int s) {
stato.setStato(s);
setChanged(); //lo stato `e cambiato
notifyObservers(stato); //passa loggetto stato
}
}
Losservatore aggiorna il suo stato con linformazione passata dal soggetto.
public class OsservatoreConcreto implements Observer {
private String Id;
private Stato stato; //lo stato dellosservatore
private SoggettoConcreto soggetto;
OsservatoreConcreto(String Id){
this.Id= Id;
stato = new Stato();
stato.setStato(Id.length());
}
public void update(Observable o, Object arg) {
if (o instanceof SoggettoConcreto){
soggetto = (SoggettoConcreto) o;
if (arg instanceof Object){
stato.setStato(stato.getStato()+ ((Stato)arg).getStato());
}
}
}
public String getId(){
return Id;
}
}
7
4.2 Il caso delle Swing
Le Swing orono svariati componenti che che rilevano eventi e sui quali `e possibile registrare osservatori. A tale
scopo un ascoltatore deve realizzare linterfaccia ActionListener implementando il metodo actionPerform-
ed(ActionEvent e). Il metodo in questione passa un evento su cui sono denite un certo numero di operazioni,
tra cui getSource() che restituisce loggetto sorgente dellevento.
Un tipico ammennicolo delle swing `e il JButton. Nellesempio che segue viene costruita una nestra (Princi-
pale) con tre bottoni, i cui nomi apriFin, chiudiFin e esci ne indicano la funzione (ordinatamente: aprire
una nestra (PopUp), chiuderla e uscire dal programma).
4.2.1 Prima realizzazione
Per convenienza si denisce la classe Finestra:
public class Finestra extends JFrame{
Finestra(String titolo){
super(titolo);
// Uscire quando viene cliccata la chiusura
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
La classe SoggOsserv che ha la funzione di contenere i soggetti (i bottoni) e losservatore prende questa
forma
public class SoggOsserv implements ActionListener{
Finestra fPrincipale;
JButton esci, apriFin, chiudiFin;
Finestra fPop = null; //finestra di PopUp
public SoggOsserv(){
fPrincipale = new Finestra("Principale");
fPrincipale.setSize(300,150);
// Si costruisce il Panel e si mettono sopra i bottoni
JPanel p = new JPanel();
apriFin = new JButton("ApriFin");
p.add(apriFin);
chiudiFin= new JButton("ChiudiFin");
p.add(chiudiFin);
esci = new JButton("Esci");
p.add(esci);
// Si registra SoggOsserv sui tre bottoni
apriFin.addActionListener(this);
chiudiFin.addActionListener(this);
esci.addActionListener(this);
// Si appiccica il Panel sulla finestra principale che viene resa visibile
fPrincipale.getContentPane().add(p);
fPrincipale.setVisible(true);
}
public void actionPerformed(ActionEvent e) {
Object obj = e.getSource();
if(obj == esci){ //verifica da che pulsante viene la nofifica
8
System.out.println("Premuto Esci");
System.exit(0);
}
if(obj==apriFin){
System.out.println("Premuto apriFin");
if (fPop==null) {
fPop=new Finestra("Finestra PopUP");
fPop.setSize(400,99);
fPop.setLocation(300,400);
fPop.setVisible(true);
}
}
if (obj==chiudiFin){
System.out.println("Premuto chiudiFin");
fPop.dispose();
fPop=null;
}
}
}
Il programma principale `e semplicemente questo:
public class Main {
public static void main(String[] args) {
SoggOsserv so =new SoggOsserv();
}
}
4.3 Seconda soluzione
Alternativamente si poteva usare il concetto di inner class e di classe anonima.
4
A tale scopo il precedente
metodo actionPerformed() pu`o essere incapsulato nella classe interna Osservatore nel modo seguente:
class Osservatore implements ActionListener{
public void actionPerformed(ActionEvent e){
// tutto come prima
}
}
Al posto della classe SoggOsserv si introduce la classe SoggOsservatori (il nime deriva dalle ragioni sotto
esposte) che non deve implementare linterfaccia ActionListener. La registrazione dellosservatore `e fatta
istanziando losservatore come classe anonima allatto della registrazione. Qui di seguito si mostrano le dierenze.
public class SoggOsservatori {
\\come prima
// Si registra SoggOsserv sui tre bottoni
apriFin.addActionListener(new Osservatore());
chiudiFin.addActionListener(new Osservatore());
esci.addActionListener(new Osservatore());
// come prima
}
Si noti che questo schema d`a luogo allistanziazione di ben tre osservatori anonimi, ognuno registrato sul cor-
rispondente bottone (in precedenza losservatore era sempre la stessa istanza della classe SoggOsserv). Ciascun
osservatore esegue solo il compito voluto per il pulsante su cui `e registrato.
4
Una inner class `e una classe denita entro unaltra. Una classe anonima `e una classe che viene istanziata ma senza denire un
riferimento.
9
Ovviamente `e possibile anche la soluzione in cui losservatore `e una classe a se stante. Con le Swing le
soluzioni precedenti sono le preferite perche hanno il pregio di essere succinte.
5 Osservazioni su StarUml
Lo schema di Figura 3 `e stato tracciato seguendo le convenzioni che di solito si usano in questi casi (ad esempio nel
libro Design Patterns). Nella classe SoggettoConcreto sono stati riportati i metodi getStato() e setStato(),
implementazioni dei corrispondenti metodi astratti di Soggetto. Il codice generato da StarUml (nella versione
disponibile alla data di scrittura di questa nota - sett 2006) per SoggettoConcreto dello schema di Figura 3 `e
questo:
public class SoggettoConcreto extends Soggetto {
public void getStato() {
}
public void setStato() {
}
public void setStato() {
}
public void getStato() {
}
}
In altre parole il disegno viene mal interpretato da StarUML. Se si vuole che venga generato un codice che non
contenga la ripetizione occorre non ripetere riportare sulle classi derivate le implementazioni dei metodi astratti,
come in Figura 5
Figura 5: Forma del diagramma che evita linconveniente osservato nella generazione del codice da parte di
StarUML.
10