Ing. del software B Il Pattern “Observer” Simone Magnolini Scopo Definire una dipendenza uno a molti fra oggetti, in modo tale che se un oggetto cambia il suo stato, tutti gli oggetti dipendenti da questo ricevano una notifica e si aggiornino automaticamente. Un esempio concreto Prendiamo ad esempio questa stessa presentazione: L'uno: la presentazione, ha uno stato (il numero della slide ad esempio) che pubblica, per questo in generale si identifica come publisher o subject. I molti: le persone presenti in aula, sono “interessate” allo stato del soggetto che hanno sottoscritto di osservare (tramite il loro ingresso in aula) per questo si identificano come subscriber o observer La relazione non è unidirezionale, chiunque può intervenire per cambiare lo stato della presentazione Doveri e responsabilità Presentazione Mantenere uno stato, cambiarlo, restituirlo Inoltre in un qualche modo quando cambia stato deve comunicare la modifica avvenuta, come ad esempio... Pubblico Reagire al cambio di stato “nel modo opportuno” Modificare lo stato di ciò che sta osservando Struttura observer Alunno 1 Presentazione reagisci() statoAlunno getState() setState() statoPresentazione Alunno 2 Alunno N reagisci() reagisci() statoAlunno statoAlunno Struttura observer Ascoltatore Presentazione reagisci() getState() setState() statoPresentazione Alunno 1 Alunno 2 Alunno N reagisci() reagisci() reagisci() statoAlunno statoAlunno statoAlunno Struttura observer Lezione Ascoltatore aggiungi(Ascoltatore) reagisci() rimuovi(Ascoltatore) notifica() Presentazione Alunno 1 Alunno 2 Alunno N getState() reagisci() reagisci() reagisci() setState() statoPresentazione statoAlunno statoAlunno statoAlunno Struttura observer Lezione Ascoltatore aggiungi(Ascoltatore) reagisci() rimuovi(Ascoltatore) notifica() Presentazione Alunno 1 Alunno 2 Alunno N getState() reagisci() reagisci() reagisci() setState() statoPresentazione statoAlunno statoAlunno statoAlunno Struttura observer Subject attach(Observer) detach(Observer) Observer update() notify() ConcreteSubject ConcreteObserver getState() update() setState() subjectstate observerState Il Pattern “Observer” Anche conosciuto come Publish-Subscribe Dependents Java Delegation Event Model Intuitivamente correlato a programmazione “a eventi” tutti i Classificazione Comportamentale Focalizzato sulle relazioni run-time tra oggetti paradigmi di Motivazione Viste multiple dello stato pubblicato Più di un formato di visualizzazione Devono essere coerenti Viste multiple interattive, che permettono la modifica dello stato Occorre aggiornare anche le altre viste Devono essere coerenti Paradigma a eventi (non c’entra per forza una GUI) Es. il meccanismo dei trigger in una base di dati relazionale Esempio classico A B C 44 33 D 333 88 A B C D 350 300 250 200 150 100 50 0 A B C D Applicabilità Se un’astrazione presenta due aspetti in cui uno è dipendente dall’altro. Separandoli è possibile riutilizzarli Se cambiamenti ad un oggetto hanno ripercussioni su altri oggetti il cui numero è variabile, o comunque non è noto a priori Se un oggetto deve inviare messaggi ad altri oggetti senza sapere esattamente di che tipo sono legami di dipendenza deboli (disaccoppiamento) Partecipanti: Subject “Conosce” tutti gli observer Chiunque implementi l’interfaccia observer può osservare il soggetto Offre metodi per: L’aggiunta di un osservatore (attach) La rimozione di un osservatore (detach) In java potrebbe essere implementata come abstract Senza istanze, ma con dei metodi implementati Partecipanti: Observer Fornisce un’interfaccia Tutti gli osservatori devono ereditarla per ottenere gli aggiornamenti del soggetto In java potrebbe essere implementata come interface Senza istanze né metodi implementati Partecipanti: ConcreteSubject Memorizza lo stato che interessa ai ConcreteObserver Lo ritorna, in generale, con il metodo getState() nel caso Java NOTA: Bisogna chiamare notify() all’interno di setState() Al termine della modifica di stato il cambiamento viene notificato a tutti gli osservatori, anche a chi l’ha prodotto Partecipanti: ConcreteObserver Mantiene un riferimento ad un oggetto ConcreteSubject di interesse In realtà un singolo osservatore può guardare più soggetti Memorizza lo stato Quello che dovrebbe essere sincronizzato/aggiornato Implementa l’interfaccia Observer Implementa il metodo update(), quest’ultimo aggiorna lo stato memorizzato per mantenere la sincronia Collaborazioni: iscrizione aConcreteSubject aConcreteObserver anotherConcrete Observer attach(this) attach(this) notify () update() getState() update() getState() Collaborazioni: aggiornamento aConcreteSubject aConcreteObserver anotherConcrete Observer setSate() notify () update() getState() update() getState() Conseguenze Disaccoppiamento tra le classi Il Subject conosce solo l’interfaccia Observer (non chi l’osserva) Per ricevere le notifiche un oggetto qualunque deve solo implementare l’interfaccia Observer Broadcast I messaggi possono essere notificati a tutti gli Observer chiamando notify() Si possono rimuovere e aggiungere Observer a piacere Il problema degli update non attesi Gli Observer non si conoscono Non possono sapere i reali effetti di operazioni compiute sul Subject Possono esserci effetti a cascata di aggiornamenti e sincronizzazioni,anche incompleti Problemi implementativi 1) Tracciare le dipendenze Mantenere le associazioni Il Subject tiene traccia di tutti i propri Observer Necessario per sapere a chi mandare le notifiche Efficiente nel caso ideale (pochi Subject, molti Observer) Il sovraccarico delle strutture dati può diventare significativo se la situazione è invertita (pochi Observer e molti Subject) Strutture associative per mappare Subject e Observer (es. Hash Table) Risparmio dello spazio, ma penalità nel tempo 2) Tanti Subject Tante cose da osservare Un Observer può essere interessato alle notifiche di più Subject Un oggetto che necessità di più strutture dati per funzionare Es. Un grafico che rappresenta le relazioni tra due entità In questo caso potrebbe essere utile passare l’oggetto come parametro della notifica per rendere evidente chi è stato modificato 3) Responsabilità Chi è il responsabile di iniziare l’invio di notifiche? Il Subject Le operazioni che modificano lo stato dell’oggetto chiamano notify() L’Observer può ignorare il problema Più operazioni di questo tipo causano molti update consecutivi Possibile inefficienza Sicuri problemi con il multithreading! Gli Observer Chiamano notify() quando hanno finito di modificare lo stato Più efficiente, più facile gestire il multithreading Più responsabilità per gli Observer, che a questo punto diventano dei client non più passivi del Subject Meno sicuro 4) Distruttori Distruzione del Subject In generale, la soluzione migliore è notificare la situazione agli Observer Non è detto che gli Observer debbano essere distrutti per forza Se “osservano” più soggetti? Modifica opportuna al distruttore della classe Subject E IN JAVA????? Altri problemi Auto-consistenza del Subject Prima del notify() tutti gli aggiornamenti devono essere completi Non utilizzare protocolli specifici di aggiornamento Modello push, il Subject inoltra informazioni sulla modifica Più efficiente, ma meno riusabile Modello pull, il Subject delega l’aggiornamento agli osservatori Meno efficiente, ma più riusabile Osservatori interessati Per migliorare l’efficienza gli Observer potrebbero fornire al momento dell’iscrizione a cosa sono interessati del Subject Quando ci sono troppi problemi ChangeManager è un’istanza del pattern Mediator ed essendo unico nell’applicazione potrebbe essere un Singleton Implementazione ESEMPIO: Un count down java 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); } } Implementazione 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)); } } } Implementazione 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); } } Ultime note In Java esistono le interfacce Observer e Observable Sono “deprecated” dalla versione 1.1 Compatibilità retroattiva Casi molto semplici, usi in cui il meccanismo a eventi è probabilmente eccessivo Java Delegation Event Model Si definiscono “listeners”, “event handlers”