Il pattern Observer (noto anche col nome Publish-Subscribe) permette di definire una dipendenza uno a molti fra oggetti, in modo tale che se un oggetto cambia il suo stato interno, ciascuno degli oggetti dipendenti da esso viene notificato e aggiornato automaticamente. L’Observer nasce dall’esigenza di mantenere un alto livello di consistenza fra classi correlate, senza produrre situazioni di forte dipendenza e di accoppiamento elevato.

 

 

Comprendiamo i passaggi per implementarlo autonomamente.

Per prima cosa, definiamo una classe NewsAgency:

 

public class NewsAgency {

    private String news;

    private List<Channel> channels = new ArrayList<>();

 

    public void addObserver(Channel channel) {

        this.channels.add(channel);

    }

 

    public void removeObserver(Channel channel) {

        this.channels.remove(channel);

    }

 

    public void setNews(String news) {

        this.news = news;

        for (Channel channel : this.channels) {

            channel.update(this.news);

        }

    }

}

 

NewsAgency è un observable, e all'aggiornamento delle notizie, lo stato di NewsAgency cambia. Quando si verifica un cambiamento, NewsAgency informa gli osservatori, denominando il loro metodo update().

 

Per poterlo fare, l'oggetto observable deve mantenere i riferimenti agli osservatori e, nel nostro caso, è la variabile dei canali.

 

Vediamo ora come può apparire l'osservatore, attraverso la classe Channel. Dovrebbe avere il metodo update() che viene invocato quando lo stato di NewsAgency cambia:

 

public class NewsChannel implements Channel {

    private String news;

 

    @Override

    public void update(Object news) {

        this.setNews((String) news);

    }

}

 

L'interfaccia Channel ha un solo metodo:

 

public interface Channel {

    public void update(Object o);

}

 

Ora, se aggiungiamo un'istanza di NewsChannel all'elenco degli osservatori e cambiamo lo stato di NewsAgency, l'istanza verrà aggiornata:

 

NewsAgency observable = new NewsAgency();

NewsChannel observer = new NewsChannel();

 

observable.addObserver(observer);

observable.setNews("news");

assertEquals(observer.getNews(), "news");

 

C'è un'interfaccia Observer predefinita nelle librerie core Java, che rende l'implementazione del pattern Observer ancora più semplice. Di seguito una illustrazione.

IMPLEMENTAZIONE CON OBSERVER

L'interfaccia java.util.Observer definisce il metodo update(), quindi non è necessario fissarlo come da sezione precedente.

Vediamo come utilizzarlo nella nostra implementazione:

 

public class ONewsChannel implements Observer {

 

    private String news;

 

    @Override

    public void update(Observable o, Object news) {

        this.setNews((String) news);

    }

}

 

Il secondo argomento viene da Observable, come vedremo di seguito.

Per definire l'observable, dobbiamo estendere la classe Observable di Java:

 

public class ONewsAgency extends Observable {

    private String news;

 

    public void setNews(String news) {

        this.news = news;

        setChanged();

        notifyObservers(news);

    }

}

 

Da notare che non è necessario chiamare direttamente il metodo update() dell'osservatore. Chiamiamo semplicemente setChanged() e notifyObservers(), e la classe Observable fa il resto per noi.

Inoltre, contiene un elenco di osservatori ed espone i metodi per mantenere tale elenco: addObserver() e deleteObserver().

Per testare il risultato, dobbiamo solo aggiungere l'osservatore a questa lista e impostare le notizie:

 

ONewsAgency observable = new ONewsAgency();

ONewsChannel observer = new ONewsChannel();

 

observable.addObserver(observer);

observable.setNews("news");

assertEquals(observer.getNews(), "news");

 

L'interfaccia di Observer non è perfetta ed è deprecata da Java 9. Uno dei suoi svantaggi è che Observable non è un'interfaccia ma una classe, ecco perché le sottoclassi non possono essere utilizzate come osservabili.

Inoltre, uno sviluppatore potrebbe sovrascrivere alcuni dei metodi sincronizzati di Observable e interrompere la loro sicurezza dei thread.

Diamo un'occhiata all'interfaccia ProperyChangeListener, che è consigliata rispetto a Observer.

IMPLEMENTAZIONE CON PropertyChangeListener

In questa implementazione, un observable deve mantenere un riferimento all'istanza PropertyChangeSupport, aiutando a inviare le notifiche agli osservatori quando viene modificata una proprietà della classe.

Definiamo observable:

 

public class PCLNewsAgency {

    private String news;

 

    private PropertyChangeSupport support;

 

    public PCLNewsAgency() {

        support = new PropertyChangeSupport(this);

    }

 

    public void addPropertyChangeListener(PropertyChangeListener pcl) {

        support.addPropertyChangeListener(pcl);

    }

 

    public void removePropertyChangeListener(PropertyChangeListener pcl) {

        support.removePropertyChangeListener(pcl);

    }

 

    public void setNews(String value) {

        support.firePropertyChange("news", this.news, value);

        this.news = value;

    }

}

 

Utilizzando questo supporto, possiamo aggiungere e rimuovere osservatori e notificarli quando lo stato dell' observable cambia:

 

support.firePropertyChange("news", this.news, value);

 

Il primo argomento è il nome della proprietà osservata. Il secondo e il terzo sono di conseguenza il vecchio e il nuovo valore.

Gli osservatori dovrebbero implementare PropertyChangeListener:

 

public class PCLNewsChannel implements PropertyChangeListener {

 

    private String news;

 

    public void propertyChange(PropertyChangeEvent evt) {

        this.setNews((String) evt.getNewValue());

    }

}

 

Grazie alla classe PropertyChangeSupport, che sta eseguendo il cablaggio per noi, possiamo ripristinare il nuovo valore della proprietà dall'evento.

Testiamo l'implementazione per assicurarci che funzioni:

 

PCLNewsAgency observable = new PCLNewsAgency();

PCLNewsChannel observer = new PCLNewsChannel();

 

observable.addPropertyChangeListener(observer);

observable.setNews("news");

 

assertEquals(observer.getNews(), "news");

 

Sono stati dunque esaminate due modalità per implementare il modello di progettazione Observer in Java, ossia quello con l’interfaccia Observer e l’approccio con PropertyChangeListener, da preferire rispetto al primo.

Articoli del tutorial