Il pattern Observer in Java

1. Panoramica

In questo articolo, descriveremo il pattern Observer e daremo un'occhiata ad alcune alternative di implementazione di Java.

2. Cos'è il pattern Observer?

Observer è un modello di progettazione comportamentale. Specifica la comunicazione tra gli oggetti: osservabili e osservatori . Un osservabile è un oggetto che notifica agli osservatori i cambiamenti nel suo stato.

Ad esempio, un'agenzia di stampa può notificare ai canali quando riceve notizie. Ricevere notizie è ciò che cambia lo stato dell'agenzia di stampa e fa sì che i canali vengano avvisati.

Vediamo come possiamo implementarlo noi stessi.

Per prima cosa, definiamo la classe NewsAgency :

public class NewsAgency { private String news; private List 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 osservabile e quando le notizie vengono aggiornate, lo stato di NewsAgency cambia. Quando avviene la modifica, NewsAgency notifica agli osservatori questo fatto chiamando il loro metodo update () .

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

Vediamo ora come può apparire l'osservatore , la classe Channel . Dovrebbe avere il metodo update () che viene richiamato 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 del canale ha un solo metodo:

public interface Channel { public void update(Object o); }

Ora, se si aggiunge un'istanza di NewsChannel alla lista degli osservatori , e cambiare lo stato di newsagency , l'istanza di NewsChannel sarà aggiornata:

NewsAgency observable = new NewsAgency(); NewsChannel observer = new NewsChannel(); observable.addObserver(observer); observable.setNews("news"); assertEquals(observer.getNews(), "news");

Esiste un'interfaccia Observer predefinita nelle librerie principali di Java, che rende l'implementazione del modello di osservazione ancora più semplice. Diamo un'occhiata.

3. Implementazione con Observer

L' interfaccia java.util.Observer definisce il metodo update () , quindi non è necessario definirlo da soli come abbiamo fatto nella sezione precedente.

Vediamo come possiamo usarlo nella nostra implementazione:

public class ONewsChannel implements Observer { private String news; @Override public void update(Observable o, Object news) { this.setNews((String) news); } } 

Qui, il secondo argomento proviene da Observable come vedremo di seguito.

Per definire l'osservabile , 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); } }

Nota 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 metodi per mantenere tale elenco: addObserver () e deleteObserver ().

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

ONewsAgency observable = new ONewsAgency(); ONewsChannel observer = new ONewsChannel(); observable.addObserver(observer); observable.setNews("news"); assertEquals(observer.getNews(), "news");

L' interfaccia Observer non è perfetta ed è deprecata a partire 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 ignorare alcuni dei metodi sincronizzati di Observable e interrompere la loro sicurezza dei thread.

Diamo un'occhiata all'interfaccia ProperyChangeListener , che è consigliata invece di usare Observer .

4. Implementazione con PropertyChangeListener

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

Definiamo l'osservabile:

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; } }

Usando questo supporto , possiamo aggiungere e rimuovere osservatori e avvisarli quando lo stato dell'osservabile cambia:

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

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

Gli osservatori dovrebbero implementare PropertyChangeListener :

public class PCLNewsChannel implements PropertyChangeListener { private String news; public void propertyChange(PropertyChangeEvent evt) { this.setNews((String) evt.getNewValue()); } }

A causa della classe PropertyChangeSupport che esegue il cablaggio per noi, possiamo ripristinare il nuovo valore della proprietà dall'evento.

Testiamo l'implementazione per assicurarci che funzioni anche:

PCLNewsAgency observable = new PCLNewsAgency(); PCLNewsChannel observer = new PCLNewsChannel(); observable.addPropertyChangeListener(observer); observable.setNews("news"); assertEquals(observer.getNews(), "news");

5. conclusione

In questo articolo, abbiamo esaminato due modi per implementare il design pattern Observer in Java, preferendo l' approccio PropertyChangeListener .

Il codice sorgente dell'articolo è disponibile su GitHub.