Observer Pattern

O

introduzione

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.

 

struttura del pattern

Il pattern Observer trova applicazione nei casi in cui diversi oggetti (Observer) devono
conoscere lo stato di un oggetto (Subject). In poche parole  abbiamo un oggetto che viene “osservato” (il subject) e tanti oggetti che “osservano” i cambiamenti di quest’ultimo (gli observers). Analizziamo in dettaglio i componenti in gioco illustrati nel seguente diagramma UML.

  • Subject: classe Observable.
    • Ha conoscenza dei propri Observer, i quali possono essere in
      numero illimitato
    • Fornisce operazioni per l’aggiunta e cancellazione di
      Observer
    • Fornisce operazioni per la notifica agli Observer.
  • Observer: interfaccia Observer
    • Specifica una interfaccia per la notifica di eventi agli oggetti
      interessati in un Subject.
  • ConcreteSubject: classe ObservedSubject.
    • mantiene lo stato del soggetto osservato e notifica gli observer in caso di un cambio di stato
    •  Invoca le operazioni di notifica ereditate dal Subject, quando
      devono essere informati i ConcreteObserver.
  • ConcreteObserver:
    • implementa l’interfaccia dell’Observer definendo il comportamento in caso di cambio di stato del soggetto osservato

 

esempio

Ho creato per voi un semplice esempio che mostra una possibile implementazione di Observer Pattern.  Vedremo come realizzare un sistema di notifiche per gli aggiornamenti di un risultato di un match sportivo. Nel nostro abbiamo :

  • Un match il cui risultato cambia nel tempo
  • N observer che vogliono abbonarsi a tale match per essere aggiornati sul risultato del match.

Mostriamo quindi il codice delle classi scritte per realizzare tale sistema.

public class ObservableMatch {
    private String matchScore;
    private List<Observer> observers = new ArrayList<>();

    public void addObserver(Observer channel) {
        //notifico il risultato iniziale non appena un observer si sottoscrive
        observer.update(this.matchScore);
        this.observers.add(channel);
    }

    public void removeObserver(Observer channel) {
        this.observers.remove(channel);
    }

    public ObservableMatch() {
        this.matchScore = "0-0";
    }

    public void setMatchScore(String newScore) {
        this.matchScore = newScore;
        for (Observer observer : this.observers) {
            observer.update(this.matchScore);
        }
    }


 ...
}

ObservableMatch è un observable: quando il matchScore è aggiornato con il relativo setter, ObservableMatch notificherà gli observer registrati a tale match chiamando il metodo update(). Per essere in grado di fare ciò, ObservableMatch ha bisogno di tenere il riferimento degli observer registrati, nel nostro caso con la lista observers. Abbiamo quindi a disposizione due metodi per gestire gli observer registrati al match:

  • addObserver: Metodo da invocare per registrare un observer. All’interno di tale metodo invochiamo  il metodo update per far ricevere all observer il risultato attuale del match, altrimenti quest’ultimo avrebbe dovuto attendere il primo aggiornamento del risultato per ricevere il risultato del match.
  • removeObserver: Rimuove la registrazione di un observer.

Vediamo adesso  l’interfaccia Observer e la sua implementazione.

public interface Observer {
    public void update(Object o);
}
public class ObserverMatch implements Observer {
    private String id;

    private String score;

    public ObserverMatch(String id) {
        this.id = id;
    }

    @Override
    public void update(Object score) {
        System.out.println("(observer-"+id+")risultato: "+ (String) score);
        this.score = (String) score;
    }

}

Il nostro observer riceve l’aggiornamento di un risultato di un match attraverso il metodo update(): il quale setta il campo score con l’ultimo score ricevuto e stampa infine a video il nuovo risultato del match. Per identificare le stampe a video abbiamo inserito un campo id per identificare i singoli observer.

A questo punto creiamo il nostro observable e due observer che si registrano per ricevere aggiornamenti sul risultato del match.

public static void main(String[] args) {
    ObservableMatch match = new ObservableMatch();
    Observer observer1 = new ObserverMatch("0");
    Observer observer2 = new ObserverMatch("1");

    match.addObserver(observer1);
    match.addObserver(observer2);

    match.setMatchScore("1-0");

    match.removeObserver(observer2);
    match.setMatchScore("2-0");

}

Se eseguiamo il main il programma stampa a video il seguente output:

(observer-0) risultato: 0-0
(observer-1) risultato: 0-0
(observer-0) risultato: 1-0
(observer-1) risultato: 1-0
(observer-0) risultato: 2-0

Analizziamo in dettaglio le singole istruzioni per comprendere l’output generato:

ObservableMatch match = new ObservableMatch();

Viene creato l’observable di un match. Lo score del match è inizializzato nel costruttore a “0-0”.

Observer observer1 = new ObserverMatch("0");
Observer observer2 = new ObserverMatch("1");

Registro due observer con id 0 e 1 al match. Non appena viene eseguito il metodo addObserver, viene invocato il metodo update dei due observer e quindi viene stampato a video il risultato di quell’istante.

(observer-0) risultato: 0-0
(observer-1) risultato: 0-0

A questo punto se aggiorniamo il risultato del match

match.setMatchScore("1-0");

I due observer vengono aggiornati e di conseguenza stampano a video il nuovo risultato.

(observer-0) risultato: 1-0
(observer-1) risultato: 1-0

Infine proviamo a rimuovere la registrazione del secondo observer per verificare che successivi aggiornamenti del match non gli vengano notificati.

match.removeObserver(observer2);
match.setMatchScore("2-0");

Come atteso, esclusivamente il primo observer viene aggiornato dopo la removeObserver del secondo observer.

(observer-0) risultato: 2-0

Il codice funzionante di questo esempio lo trovate nel seguente link al mio repository github: LINK REPO GITHUB.

java.util.Observer

A fini didattici abbiamo implementato da zero le interfacce e classi necessarie allo sviluppo del pattern Observer. Occorre sapere però che il core di java mette a disposizioni :

Pertanto avremmo potuto eliminare la nostra interfacce Observer e utilizzare quella del core java ed infine estendere la classe Observable del core la quale mette già a disposizione i metodi per aggiungere/rimuovere un observer e notificarlo.

conclusioni

Questo pattern è molto utilizzato: viene impiegato in molte librerie, nei toolkit delle GUI, nel pattern architetturale MVC, nei sistemi di messaggistica e realtime. Il motivo di questo successo? Elenchiamo i vantaggi noti:

  • Il pattern costituisce un ottimo esempio di applicazione del noto principio “Fare in modo che l’accoppiamento tra oggetti sia il meno stretto (dall’ inglese “loosely”) possibile”.
  • Consente di inviare dati ad altri oggetti in modo efficace senza alcuna modifica nelle classi Subject o Observer.
  • I Subject e Observer sono indipendenti e una modifica di uno di questi componenti non richiede la modifica dell’altro componente.
  • Gli Observers possono essere aggiunti /rimossi in qualsiasi momento

 

Come in ogni mio articolo su design pattern cerco di elencare anche possibili svantaggi che bisogna considerare quando si fa uso dell’observer pattern:

  • La classe core di Java Observable impone l’utilizzo dell’ereditarietà rispetto alla programmazione su interfaccia.
  • Se non usato con attenzione, tale pattern può aggiungere complessità non necessaria. Occorre tenere presente che è delegato al client l’avvio della notifica di aggiornamento degli observer (nel nostro esempio il metodo setMatchScore) e che ogni aggiornamento notificherà tutti gli observer; pertanto occorre stare attenti ad invocare una volta sola per aggiornamento per evitare la duplicazione degli aggiornamenti inviati.
  • L’ordine delle notifiche di Observer è inaffidabile come riporta la documentazione della classe Observable di Java.

In Java 9 Oracle ha quindi deciso di deprecare Observable: troviamo nella documentazione ufficiale la segente comunicazione

La classe Observable e l’interfaccia Observer sono state deprecate in Java 9 perché il modello di eventi supportato da Observer and Observable è piuttosto limitato, l’ordine delle notifiche fornite da Observable non è specificato e le modifiche di stato non sono in corrispondenza uno a uno con le notifiche .

L’alternativa valida promossa in java 9 è quella basata su Reactive Stream/Flow Api: uno standard per l’elaborazione di stream asincroni non bloccanti basata sulla programmazione reactive (note implementazioni di tale paradigma sono ad  esempio RxJava o Akka-Streams). Ma non vi preoccupate, vi introdurrò questi nuovi concetti in un prossimo articolo.

 

 

 

A proposito di me

Dario Frongillo

Software architect con un forte background in Java, Architettura REST, framework Spring , Database Design,progettazione e sviluppo di SPA e RIA Web application con framework Javascript. Attualmente mi occupo di sviluppo soluzioni software in ambito Banking e Finance: in particolare progettazione e sviluppo di applicativi web based per la realizzazione di sistemi di trading, interfacce con i mercati finanziari e di servizi real time.

Gli articoli più letti

Articoli recenti

Commenti recenti