Home > GOF Pattern, java > GoF Patterns: Observer

GoF Patterns: Observer

3 marzo 2012

Translate in English with Google Translate
In questo articolo tratterò il pattern Observer anche detto Dependents o Publish-Subscribe

Motivazione
Si tratta di un pattern comportamentale basato su oggetti che viene utilizzato quando si vuole realizzare una dipendenza uno-a-molti in cui il cambiamento di stato di un soggetto venga notificato a tutti i soggetti che si sono mostrati interessati.
Un esempio molto semplice è rappresentato dalle newsletters in cui gli utenti interessati a degli argomenti inseriscono il loro indirizzo email ed a fronte di novità inerenti gli argomenti, riceveranno una email di notifica. In questo modo viene applicata una gestione ad eventi, cioè al verificarsi di una notizia i soggetti interessati verranno informati tramite email. In questo modo l’interessato evita di fare polling, cioè evita di fare continue richieste al soggetto osservato per sapere se è avvenuto o meno un cambiamento ma al contrario verrà notificato in push dal soggetto osservato nel caso in cui dovesse interviene una modifica.

Questo pattern viene impegato in molte librerie, nei toolkit delle GUI e nel pattern architetturale MVC.

Nel pattern MVC abbiamo la presenza di 3 soggetti: il Model, la View ed il Controller. Questi soggetti svolgono compiti diversi e tra di loro è presente una separazione di responsabilità c.d. “separation of concern”. Ma c’è da dire che tra di loro esiste un forte legame in merito al cambiamento di stato. In particolare il Controller è interessato ai cambiamenti di stato della View, mentre la View è interessata ai cambiamenti di stato del Model. Questo comporta che nel caso in cui dovessero avvenire dei cambiamenti il Model notifica alla View mentre la View notifica al Controller. Quindi il pattern Observer trova applicazione 2 volte nell’MVC su coppie di soggetti diversi (Model-View e View-Controller). La View svolge un ruolo doppio poichè si trova ad essere osservata dal Controller e nello stesso tempo ad essere osservatore nei confronti del Model. A differenza del Model e del Controller che invece giocano un ruolo singolo , infatti il Model è osservato dalla View mentre il Controller è un osservatore della View.

Il ruolo di osservatore è il ruolo svolto da colui che si mostra interessato ai cambiamenti di stato, c.d. Observer. Il ruolo di osservato è il ruolo svolto da colui che viene monitorato, c.d. Subject o Observable.

Partecipanti e Struttura
Questo pattern è composto dai seguenti partecipanti:

  1. Subject: espone l’interfaccia che consente agli osservatori di iscriversi e cancellarsi; mantiene una reference a tutti gli osservatori iscritti
  2. Observer: espone l’interfaccia che consente di aggiornare gli osservatori in caso di cambio di stato del soggetto osservato.
  3. ConcreteSubject: mantiene lo stato del soggetto osservato e notifica gli osservatori in caso di un cambio di stato.
  4. ConcreteObserver: implementa l’interfaccia dell’Observer definendo il comportamento in caso di cambio di stato del soggetto osservato

Conseguenze
Tale pattern presenta i seguenti vantaggi/svantaggi:

  • Astratto accoppiamento tra Subject e Observer: il Subject sa che una lista di Observer sono interessati al suo stato ma non conosce le classi concrete degli Observer, pertanto non vi è un accoppiamento forte tra di loro.
  • Notifica diffusa: il Subject deve notificare a tutti gli Observer il proprio cambio di stato, gli Observer sono responsabili di aggiungersi e rimuoversi dalla lista.

Vediamo come si presenta il Pattern Observer utilizzando il Class Diagram in UML:

Implementazione
Primo esempio: il caso in cui due osservatori sono interessati al cambio di stato di un soggetto osservato. Successivamente uno di essi cancella la sottoscrizione e non riceve più notifiche. In questo caso dobbiamo creare il Subject e l’Observer e le classi concrete ConcreteSubject e ConcreteObserver.

Vediamo come si presenta il pattern in UML in base all’esempio:


L’interfaccia dell’Observer definisce il metodo che dovrà essere implementato dagli osservatori.
Pertanto quando interverranno delle modifiche al soggetto osservato, verrà invocato il metodo update() di tutti gli osservatori.

package patterns.observer;

public interface Observer {

    public void update();

}

Il Subject include una lista degli osservatori che si registrano presso il soggetto osservato tramite i metodi addObserver() e si cancellano tramite il metodo removeObserver(). Mentre invece il metodo notifyObservers() viene invocato dalla classe concreta ConcreteSubject quando interviene un cambio di stato.

package patterns.observer;

import java.util.ArrayList;
import java.util.List;

public abstract class Subject {

    private List<Observer> list = new ArrayList<Observer>();

    public void addObserver(Observer observer) {
        list.add( observer );
    }

    public void removeObserver(Observer observer) {
        list.remove( observer );
    }

    public void notifyObservers() {
        for(Observer observer: list) {
            observer.update();
        }
    }

}

Il ConcreteObserver implementa il metodo update() per definire l’azione da intraprendere quando interviene un cambio di stato del Subject.

package patterns.observer;

public class ConcreteObserver implements Observer {

    @Override
    public void update() {
        System.out.println("Sono " + this + ": il Subject e' stato modificato!");
    }

}

Il ConcreteSubject definisce lo stato del Subject concreto e l’invocazione degli osservatori in caso di cambio di stato.

package patterns.observer;

public class ConcreteSubject extends Subject {

    private boolean state;

    public void setState(boolean state) {
        this.state = state;
        notifyObservers();
    }

    public boolean getState() {
        return this.state;
    }

}

Creiamo la classe Client che si occupa di creare il soggetto da osservare e due osservatori che si registrano per essere notificati in caso di cambio di stato del soggetto osservato. Successivamente rimuoviamo un osservatore e cambiamo lo stato del soggetto osservato per notare che non riceverà più notifiche.

package patterns.observer;

public class Client {

    public static void main(String[] args) {
        ConcreteSubject subject = new ConcreteSubject();
        Observer observer1 = new ConcreteObserver();
        Observer observer2 = new ConcreteObserver();

        //aggiungo 2 observer che saranno notificati
        subject.addObserver(observer1);
        subject.addObserver(observer2);

        //modifico lo stato
        subject.setState( true );

        //rimuovo il primo observer che non sarà + notificato
        subject.removeObserver(observer1);

        //modifico lo stato
        subject.setState( false );

    }

}

L’output del nostro esempio viene visualizzato di seguito:

$JAVA_HOME/bin/java patterns.Client
Sono pattern.observer.ConcreteObserver@4a5ab2: il Subject e' stato modificato!
Sono pattern.observer.ConcreteObserver@1888759: il Subject e' stato modificato!
Sono pattern.observer.ConcreteObserver@1888759: il Subject e' stato modificato!

Secondo esempio: nelle librerie Java il Subject e l’Observer sono già presenti con le classi java.util.Observable e java.util.Observer.

Pertanto, per non reinventare la ruota ogni volta, possiamo utilizzare queste classi per il nostro esempio. Procediamo con il refactoring del nostro codice per adattarlo alle classi java.util.Observable e java.util.Observer.

Vediamo come si presenta il nostro Class Diagram in UML:

Modifichiamo la classe ConcreteObserver implementando l’interfaccia Observer che definisce il metodo update() con 2 parametri: il soggetto osservato Observable ed un Object utile a passare degli argomenti.

package patterns.observer;

import java.util.Observable;
import java.util.Observer;

public class ConcreteObserver implements Observer {

    @Override
    public void update(Observable o, Object arg) {
        System.out.println("Sono " + this + ": il Subject e stato modificato!");
    }

}

Modifichiamo la classe ConcreteSubject in modo che estenda la classe Observable. Nel metodo setState() occorre invocare anche il metodo setChanged() che esprime la volontà di notificare gli osservatori. Infatti se commentiamo questo metodo, gli osservatori non saranno notificati nonostante l’invocazione del metodo notifyObservers(). Ciò avviene in quanto nel metodo notifyObservers() è presente una semplice condizione “if (!changed)” che forza l’uscita dal metodo qualora il cambio non venga confermato.

package patterns.observer;

import java.util.Observable;

public class ConcreteSubject extends Observable {

    private boolean state;

    public void setState(boolean state) {
        this.state = state;
        setChanged();
        notifyObservers();
    }

    public boolean getState() {
        return this.state;
    }

}

Adesso vediamo il codice del metodo notifyObservers() della classe java.util.Observable:

    public void notifyObservers(Object arg) {
        Object[] arrLocal;
        synchronized (this) {
            if (!changed)
                return;
            arrLocal = obs.toArray();
            clearChanged();
        }
        for (int i = arrLocal.length-1; i>=0; i--)
            ((Observer)arrLocal[i]).update(this, arg);
    }

Modifichiamo il codice del Client importando java.util.Observer e rinominando il metodo deleteObserver() invece di removeObserver() dell’esempio precedente:

package patterns.observer;

import java.util.Observer;

public class Client {

    public static void main(String[] args) {
        ConcreteSubject subject = new ConcreteSubject();
        Observer observer1 = new ConcreteObserver();
        Observer observer2 = new ConcreteObserver();

        //aggiungo 2 observer che saranno notificati
        subject.addObserver(observer1);
        subject.addObserver(observer2);

        //modifico lo stato
        subject.setState( true );

        //rimuovo il primo observer che non sarà + notificato
        subject.deleteObserver(observer1);

        //modifico lo stato
        subject.setState( false );

    }

}

Il risultato non cambia ma abbiamo ridotto il LOC (Lines Of Code) ed abbiamo usato meglio le librerie in nostro possesso:

$JAVA_HOME/bin/java patterns.Client
Sono patterns.observer.ConcreteObserver@f62373: il Subject e' stato modificato!
Sono patterns.observer.ConcreteObserver@19189e1: il Subject e' stato modificato!
Sono patterns.observer.ConcreteObserver@f62373: il Subject e' stato modificato!
Categorie:GOF Pattern, java
  1. Anonimo
    3 luglio 2013 alle 9:20 PM

    Grazie, ottimi esempi da studiare!

  2. Anonimo
    22 luglio 2013 alle 5:57 PM

    Ottimi tutti gli esempi sui PAttern. Grazie

    • 22 luglio 2013 alle 6:28 PM

      Mi fa molto piacere sapere che ti siano utili.🙂

  3. Franzi90
    17 agosto 2013 alle 5:22 PM

    Esempio stupendo. So che forse è chiedere molto, ma potresti fare un esempio più corposo usando sempre Observer/Observable ma sul MVC?

    • 21 agosto 2013 alle 3:24 AM

      Ci pensai quando scrissi l’articolo, ma lo evitai per vari motivi.
      Prima di tutto perchè l’esempio sull’MVC sarebbe stato un po’ lungo e avrebbe allungato troppo l’articolo, come dici anche tu sarebbe stato “più corposo”.
      Secondo perchè non volevo dilungarmi su di un pattern architetturare mentre definivo un design pattern, spostavo troppo l’attenzione.
      Terzo perchè l’MVC merita un articolo a parte e sicuramete ho intenzione di trattarlo a breve.
      Infine, per quanto tra MVC e Observer ci sia un legame forte, non è detto che l’MVC si concretizzi con l’Observer, difatti Struts non lo usa!
      http://wiki.apache.org/struts/StrutsDesignPatterns pertanto è solo un modo per implementarlo.

  4. 21 marzo 2014 alle 11:13 AM

    Complimenti ottimo articolo, grazie.

  1. No trackbacks yet.
I commenti sono chiusi.
%d blogger cliccano Mi Piace per questo: