Pattern Observer
Il pattern Observer
• Il pattern Observer entra in gioco quando diversi
oggetti devono conoscere i cambiamenti di stato di
un oggetto particolare
• C'è’ un oggetto che deve essere costantemente
monitorato (detto subject) da altri oggetti che
osservano i i cambiamenti di quest'ultimo (detti
observers).
• Il pattern Observer è di tipo comportamentale
Meccanismo del pattern Observer
Il pattern “Observer” assegna il compito di
notificare i suoi cambi di stato all’oggetto
monitorato stesso (Subject), attraverso dei
riferimenti agli oggetti che devono essere
avvisati (ConcreteObservers) tramite
l’invocazione a un loro metodo, presente
nella interfaccia che devono implementare
(Observer).
Il pattern Observer
Il problema affrontato
Il pattern Observer trova applicazione nei casi
in cui diversi oggetti (Observer) devono
conoscere lo stato di un oggetto (Subject)
Motivazione
Mantenere la consistenza tra oggetti collegati,
riducendo però l'accoppiamento tra di essi
Struttura del pattern Observer
Partecipanti
Interfaccia Subject Definisce la classe che deve essere
“osservata”, conosce i suoi osservatori (in qualsiasi
numero). Fornisce operazioni per l’addizione e
cancellazione di oggetti osservatori
Interfaccia Observer. Specifica una interfaccia per la
notifica di eventi agli oggetti interessati ad un Subject.
ConcreteSubject: Invoca le operazioni di notifica ereditate
dal Subject, quando devono essere informati i
ConcreteObserver dei suoi cambiamenti.
ConcreteObserver: Implementa l’operazione di
aggiornamento dell'Observer.
Applicabilità del pattern Observer
Usare il pattern Observer quando:
• In un problema ci sono due aspetti tra loro
dipendenti, che possono essere rappresentati come
classi che possono essere usati indipendentemente
tra loro
• Quando il cambiamento di un oggetto provoca un
cambiamento in un altro oggetto
• Quando un oggetto ha la necessità di comunicare
con altri oggetti, senza fare assunzioni sugli altri
oggetti
Il pattern Observer in Java
Nel package java.util sono presenti la classe
Observable (soggetto) e l'interfaccia Observer. La
classe Observable registra gli osservatori e notifica i
cambiamenti avvenuti agli Observer attraverso il
metodo notifyObservers(Object args) preceduto da
una chiamata al metodo setChanged() :
public class Subject extends Observable {
public void startProcess() {
setChanged(); notifyObservers("Processo iniziato");
//== do something
setChanged(); notifyObservers("Processo concluso");}
}
Gli Observer sono conservati con un Vector
l'interfaccia Observer contiene un unico metodo update:
class Osservatore implements Observer {
public void update(Observable obs, Object args) {…
}
}
Il metodo update ricevere un argomento (args) per scopi
generali, e il riferimento all'oggetto Observable che ha
notificato l'evento.
Un Observer può a sua volta registrarsi a più Subject.
Metodi di Subject
void addObserver( Observer o ): registra l’ Observer nel suo elenco
interno di oggetti da notificare.
protected void setChanged(): registra il cambiamento di stato
boolean hasChanged(): restituisce true se l’oggetto ha cambiato stato
(setChanged)
void notifyObservers(): se l’oggetto è cambiato notifica tutti gli
Observer poi chiama il metodo clearChanged per segnare che l’oggetto è nello
stato “non cambiato”.
void notifyObservers( Object arg ): oltre alla notifica invia ad ogni
Observer un oggetto come secondo parametro del metodo update.
protected void clearChanged(): impone lo stato “non cambiato”
int countObservers(): il numero di Observer registrati.
void deleteObservers(): cancella l’elenco degli Observer registrati.
void deleteObservers(Observer o): cancella un Observer o
dall’elenco degli observer.
Come avviene la registrazione degli observer:
Osservatore obs = new Osservatore();
Subject subject = new Subject();
subject.addObserver(obs);
subject.startProcess();
Pattern Observer conseguenze
• Benefici
Minimizzazione dell'accoppiamento tra Observer e
Subject, che possono essere usati
indipendentemente gli uni dagli altri. Gli Observer
possono essere aggiunti senza modificare il Subject.
Il Subject conosce solo la lista dei suoi Observer, e
non le classi concrete degli Observer, ma solo
l'intefaccia. Subject e Observer possono appartenere
a diversi livelli di astrazione.
• Event Broadcasting, gli observer possono essere
aggiunti a run time e possono regolarsi se reagire o
meno ad una notifica del subject
Svantaggi
Possibile cascata di notifiche. Poiché gli observer
mutuamente si ignorano, una richiesta di modifica può
avere effetti incontrollati, scatenando la reazione degli altri
observer.
Essendo molto semplice l'interfaccia di notifica, risulta
ostico stabilire il tipo di modifica che è avvenuta nel
subject, e quindi per gli Observer regolarsi di conseguenza
Dettagli di implementazione
• Come il subject conserva gli observer
Array, Collection (LinkedList)
• Come realizzare un meccanismo di osservazione di
più Subject (Observer registrato a più Subject)?
I metodo update deve notificare anche la natura del
Subject (args->Object)
• Chi richiede le notifiche? Il Subject stesso, quando
cambia lo stato; gli Observer, in conseguenza delle
loro necessità (in questo modo si eviterebbero le
notifiche inutili) ; oppure un terzo attore
• Assicurarsi che i Subject effettuino le notifiche dopo i
loro cambi di stato
• Quanta informazione passare nei metodi update?
– Modalità push → il Subject invia informazioni
dettagliate sulla notifica
– Modalità pull, il Subject non ivia alcuna informazione
Nel primo caso gli Observer sono meno riusabili
• Gli Observer possono specificare alcuni eventi di
interesse su cui ricevere le notifiche (modello publish
– subscrive)
• Per far si che un Observer ricevi una notifica dopo
che un certo numero di Subject abbia cambiato stato
si può usare un mediator, che raccoglie le notifiche
dei Subject, li elabora e poi li passa agli Observer
Esempio: downloader
Costruiamo una classe in grado di scaricare una risorsa da
internet, pero’ di comunicare anche il proprio stato ad
osservatori utilizzando il pattern Observer.
• Gli stati di interesse possono essere:
1)Stabilire la connessione alla risorsa,
2)Il download della risorsa (lettura dello stream)
3)La fine del download
4)Un problema durante lo scaricamento
Lo stato puo’ essere trasmesso, informa di oggetto, proprio
attraverso l’ argomento del metodonotify che il subject invia
agli observers comprendano il tipo di notifica che ricevono.
public class MsgObserver {
public final static int START = 0;
public final static int DOWNLOAD = 1;
public final static int READLINE = 2;
public final static int END = 3;
public final static int EXCEPTION = 4;
public int code;
public Object msg;
public MsgObserver(int code, Object msg)
{ this.code = code; this.msg = msg; }
}
Il Subject, oltre al tipo di messaggio, può anche inviare
anche altre informazioni, come la percentuale di
download raggiunta, oppure un oggetto di tipo Exception
se si verifica un'eccezione.
•
La classe Observable (il Subject)
public class DownloadPageSubject extends Observable
{ private URL url;
private String html = null;
public DownloadPageSubject(String path)
{ try { url = new URL(path);}
catch(MalformedURLException ex)
{ changedState(new MsgObserver(MsgObserver.EXCEPTION,
ex));
//Notifichiamo l'eccezione
}
}
public String getHtml() { return html; }
private void changedState(MsgObserver msg)
{ setChanged(); notifyObservers(msg); }
//== Metodo download l'attività del subject
public void download() {
try {
changedState(new MsgObserver(MsgObserver.START, null));
//Inizio connessione
URLConnection connection = url.openConnection();
changedState(new
MsgObserver(MsgObserver.DOWNLOAD,null));
//Connessione avvenuta
BufferedReader read = new BufferedReader( new
InputStreamReader(connection.getInputStream()));
String line = read.readLine();
while(line!=null) {
changedState(new MsgObserver(MsgObserver.READLINE,
line));
//Lettura di una riga
line = read.readLine();
if (line!=null) { html+=line; }
}
changedState(new MsgObserver(MsgObserver.END, null));
//Fine processo
} catch(IOException ex) {
changedState(new MsgObserver(MsgObserver.EXCEPTION,
ex));
//Notifichiamo l' eccezione
}}}
Un possibile observer potrebbe essere un oggetto che da informazioni
sullo stato del download stampando i messagi sulla console di java:
class ConsoleObserver implements Observer {
public void update(Observable o, Object arg)
{ MsgObserver message = (MsgObserver)arg;
if(message.code==MsgObserver.START)
{ System.out.println("Connesione in corso..."); }
else if(message.code==MsgObserver.DOWNLOAD)
{ System.out.println("Connesione OK"); }
else if(message.code==MsgObserver.END)
{ System.out.println("Download terminato"); }
}
}
• Un'altro observer invece può essere una JtextArea
(swing) che stampa solo le righe che man mano vengono
lette, quindi in questo caso identifica solo i messaggi
READLINE e stampa la stringa che il subject incapsula
nell' oggetto MsgObserver:
class TextAreaObserver extends JTextArea
implements Observer {
public void update(Observable o, Object arg)
{ MsgObserver message = (MsgObserver)arg;
if (message.code == MsgObserver.READLINE)
super.append((String)message.msg+"\n");
}
Limitazioni del modello Java
Observer
• La classe Observer impedisce di fatto che
l'implementazione dell'Observer possa
ereditare da qualche altra classe
• L'Observer e il Subject devono coesistere nello
stesso thread
• Per aggirare il problema dell'ereditarietà si
puo' usarre il meccanismo della “Delegation”
Si crea uno SpecialSubject della gerarchia che contiene
un oggetto Observable
Si delega il comportamento del Subject (Observable) di
cui lo SpecialSubject ha bisogno al suo oggetto
Observable (il delegato)
Si perfeziona (Override) il metodo dell'oggetto che
deve essere osservato
Esempio: Observed Employee
Supponiamo di dover realizzare in Observer che deve
essere informato degli aumenti di stipendio di un impiegato
class Employee
{
String name;
double salary;
Employee()
{ salary=0; name="";
}
Employee(String _name, double _salary)
{
name=_name;
salary=_salary;
}
public void raiseSalary(double amount)
{
salary+=amount;
System.out.println("Employee:new salary:"+salary);
}
}
Creiamo una sottoclasse di Employee che possiede un
oggetto Observable, cui deleghiamo il meccanismo di
notifica
class ObservedEmployee extends Employee {
//== OGGETTO DELEGATO PER LE NOTIFICHE AGLI OBSERVER
private Observable obs;
public ObservedEmployee(String name, double salary) {
super(name,salary);
obs = new DelegatedObservable();
}
public Observable getObservable() {return obs;}
public void raiseSalary(double amount) {
//== METODO DA MONITORARE CON NOTIFICHE AGLI OBSERVER
super.raiseSalary(amount);
obs.setChanged();
obs.notifyObservers(new Double(amount));
}
}
L'utilizzo diretto dell' oggetto Observable, cui deleghiamo il
meccanismo di notifica, non è in realtà possibile perché i
metodi setChanged e clearChanged
che servono per il meccanismo del pattern, sono protetti,
non pubblici. Quindi non possono essere utilizzati in una
classe che non sia ereditata da Obervable.
Soluzione
Creare una classe DelegateObservable e fare l'ovverride dei
metodi protected modificando la loro visibilità a public
class DelegatedObservable extends Observable {
public void clearChanged() { super.clearChanged(); }
public void setChanged() { super.setChanged();}
}
//== use del delegateObservable
class ObservedEmployee extends Employee {
...
private DelegatedObservable obs;
...
}
class SalaryObserver implements Observer {
private double salary_change;
public SalaryObserver() { salary_change = 0; }
public void update(Observable obj, Object arg) {
if (arg instanceof Double) {
salary_change = ((Double)arg).doubleValue();
System.out.println("salary amount variation:" + salary_change);
} else {
System.out.println("SalaryObserver: Unkonwn change!");
}
}
}
public class test {
public static void main(String args[]) {
//== Crea il Subject e Observers.
ObservedEmployee e = new ObservedEmployee("A.Bell",
1200);
SalaryObserver salaryObs = new SalaryObserver();
//== Add those Observers!
e.getObservable().addObserver(salaryObs);
//== change and notify Subject.
e.raiseSalary(200);
}
}
• E' possibile completare la classe ObservedEmployee con
altri metodi di Observable, in modo da avere una
implementazione più sicura e controllata.
public void addObserver(Observer o) {
obs.addObserver(o);
}
public void deleteObserver(Observer o) {
obs.deleteObserver(o);
}
In questo modo ObservedEmployee farebbe da
wrapper per il suo contenuto Observable senza esporlo
direttamente
Il modello ad Eventi di Awt
• Il package “Abstract Window Toolkit” di Java
(1.1) mette a disposizione una serie di
interfacce e classi per la programmazione
delle interfacce grafiche. Swing che è parte
della JFC (Java Foundation Class, da Java 2) è
un versione più sofisticata di GUI, ma non
supportata da tutte le distribuzioni Java
Java (da 1.1) adotta un modello per gli eventi della GUI proprio basato
sul pattern Observer
Gli event source sono i componenti che nella GUI possono generare
eventi (->ConcreteSubject)
Gli oggetti cui devono essere notificati gli eventi si chiamano event
listeners (->ConcreteObserver)
Rispetto al pattern Observer, che prevede una sola interfaccia, il
modello eventListner prevede 11 differenti interfacce di eventListner,
dedicate a diverse famiglie di eventi
ActionListener
ComponentListener
AdjustmentListener
ContainerListener
ItemListener
FocusListener
TextListener
KeyListener
MouseMotionListener
MouseListener
WindowListener
Questi Listener hanno dei metodi specializzati che devono
essere implementati (in quanto interfacce), mentre può
accadere che un event listner sia interessato ad un evento in
particolare (es Window Closing), o comunque a non tutti gli
eventi dell'interfaccia.
Java fornisce delle classi “adapter” per assecondare queste
richieste: La classe WindowAdapter implementa l'interfaccia
WindowListner fornendo una implementazione dummy (vuota)
per ciascuno dei metodi della interfaccia. La classe che eredita
da WindowAdapter può specificare (override) solo i metodi di
interesse.
(es)