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)