CodeGym /Corsi /JAVA 25 SELF /Pattern Observer (osservatore)

Pattern Observer (osservatore)

JAVA 25 SELF
Livello 50 , Lezione 3
Disponibile

1. Conosciamo il pattern «Osservatore»

Il pattern «Osservatore» (Observer) è uno dei pattern di progettazione più noti e fondamentali. Descrive la situazione in cui un oggetto (l’osservato, ovvero il subject) comunica le proprie modifiche ad altri oggetti (gli osservatori, observers) che si sono iscritti a tali cambiamenti.

Detto più semplicemente: abbiamo un canale Telegram (l’oggetto osservato) e ci sono «iscritti» (gli osservatori). Ogni volta che esce un nuovo post, il canale notifica tutti gli iscritti, i quali decidono cosa farne — leggere, ignorare o disiscriversi.

In programmazione, questo pattern consente di notificare automaticamente gli oggetti interessati di eventi o cambiamenti di stato, senza collegarli direttamente tra loro. Questo è importante per costruire sistemi flessibili, estendibili e di facile manutenzione.

Dove si incontra il pattern «Osservatore»?

  • Nelle interfacce grafiche (Swing, AWT, JavaFX) — listener di eventi.
  • Nelle librerie reattive (RxJava, Project Reactor).
  • Nella logica di business: reagire al cambiamento dello stato del modello.
  • Nei motori di gioco (eventi di collisione, vittoria, sconfitta, ecc.).
  • Ovunque serva separare «che cosa è successo» da «che cosa farne».

Relazione del pattern con eventi e listener in Java

Di fatto, l’intero modello a eventi di Java si basa su «Osservatore». Quando scrivete button.addActionListener(listener);, state applicando questo pattern:

  • Osservato — il pulsante (o un altro componente).
  • Osservatore — il vostro listener che implementa il metodo actionPerformed().
  • Evento — l’utente ha cliccato, ha passato il mouse, ecc.
  • Notifica — il componente invoca actionPerformed().

Tutto questo è una realizzazione classica di Observer!

2. Implementazione classica del pattern «Osservatore»

Vediamo come implementare il pattern nelle nostre classi — senza Swing e AWT — per verificare che non ci sia alcuna magia.

Elementi principali del pattern

  • Observable (Subject) — l’oggetto osservato. Mantiene un elenco di osservatori e li notifica delle modifiche.
  • Observer — l’interfaccia dell’osservatore, in genere con il metodo update().

Esempio: termometro e condizionatore

Interfaccia dell’osservatore

public interface TemperatureObserver {
    void temperatureChanged(int newTemperature);
}

Classe «Termometro» (osservato)

import java.util.*;

public class Thermometer {
    private int temperature;
    private final List<TemperatureObserver> observers = new ArrayList<>();

    public void addObserver(TemperatureObserver observer) {
        observers.add(observer);
    }

    public void removeObserver(TemperatureObserver observer) {
        observers.remove(observer);
    }

    public void setTemperature(int newTemperature) {
        if (this.temperature != newTemperature) {
            this.temperature = newTemperature;
            notifyObservers();
        }
    }

    private void notifyObservers() {
        for (TemperatureObserver observer : observers) {
            observer.temperatureChanged(temperature);
        }
    }
}

Esempio di osservatore — «Condizionatore»

public class AirConditioner implements TemperatureObserver {
    @Override
    public void temperatureChanged(int newTemperature) {
        if (newTemperature > 25) {
            System.out.println("Condizionatore acceso! Fa caldo: " + newTemperature + "°C");
        } else {
            System.out.println("Condizionatore spento. Temperatura: " + newTemperature + "°C");
        }
    }
}

Uso

public class Main {
    public static void main(String[] args) {
        Thermometer thermometer = new Thermometer();
        AirConditioner conditioner = new AirConditioner();

        thermometer.addObserver(conditioner);

        thermometer.setTemperature(22); // Condizionatore spento. Temperatura: 22°C
        thermometer.setTemperature(28); // Condizionatore acceso! Fa caldo: 28°C
    }
}

Ecco tutta la magia! Si possono aggiungere anche cento osservatori — tutti riceveranno una notifica quando cambia la temperatura.

Schema grafico del pattern

flowchart LR
    T["Termometro (Observable)"] -- notifica --> AC["Condizionatore (Observer)"]
    T -- notifica --> L["Logger (Observer)"]
    T -- notifica --> Alarm["Allarme (Observer)"]

Dettagli moderni: Observable deprecato e nuovi approcci

Nella libreria standard di Java esistevano java.util.Observable e java.util.Observer, ma da Java 9 sono contrassegnati come deprecati (deprecated). Il motivo è la flessibilità insufficiente (ad esempio, Observable è una classe e non un’interfaccia, per cui è più difficile estendere un’altra classe).

L’approccio moderno è progettare interfacce di listener proprie e la logica di iscrizione/disiscrizione (come nell’esempio sopra). È più flessibile, sicuro e meglio aderente alle esigenze reali.

3. Esempio: mini‑app con sottoscrittori

Creiamo un «contatore di clic» con la possibilità di iscriversi al cambiamento del valore.

Interfaccia del listener

public interface CounterListener {
    void counterChanged(int newValue);
}

Classe‑contatore

import java.util.*;

public class Counter {
    private int value = 0;
    private final List<CounterListener> listeners = new ArrayList<>();

    public void addCounterListener(CounterListener l) {
        listeners.add(l);
    }

    public void removeCounterListener(CounterListener l) {
        listeners.remove(l);
    }

    public void increment() {
        value++;
        notifyListeners();
    }

    private void notifyListeners() {
        for (CounterListener l : listeners) {
            l.counterChanged(value);
        }
    }

    public int getValue() {
        return value;
    }
}

Listener: stampiamo un messaggio

public class ConsoleCounterListener implements CounterListener {
    @Override
    public void counterChanged(int newValue) {
        System.out.println("Il contatore è cambiato: " + newValue);
    }
}

Uso

public class Main {
    public static void main(String[] args) {
        Counter counter = new Counter();
        counter.addCounterListener(new ConsoleCounterListener());

        counter.increment(); // Il contatore è cambiato: 1
        counter.increment(); // Il contatore è cambiato: 2
    }
}

4. Dettagli utili

Alternative moderne ed estensioni

Nei progetti reali si usano spesso classi anonime o espressioni lambda per iscriversi: counter.addCounterListener(newValue -> System.out.println("Nuovo valore: " + newValue));

(Per farlo, l’interfaccia deve essere funzionale — avere un unico metodo astratto.)

Sono popolari anche le librerie reattive (RxJava, Project Reactor), dove «Osservatore» è implementato con supporto di flussi di eventi, filtraggio, asincronia, ecc. Per capirne l’essenza è sufficiente lo schema classico visto sopra.

Applicazioni del pattern «Osservatore» nella vita reale

  • Modelli di dati. La modifica del modello (elenco di attività, prodotti, utenti) notifica le viste per l’aggiornamento.
  • Logging. Un logger sottoscritto reagisce agli eventi in tutto il sistema.
  • Notifiche. Al cambiamento di stato — invio di email, push notification, messaggi su Telegram.
  • Giochi. Modifica della salute, comparsa di un nemico, completamento del livello.
  • Multithreading. Un thread pubblica eventi, altri reagiscono.

5. Errori tipici nell’implementazione del pattern «Osservatore»

Errore n. 1: ci si è dimenticati di rimuovere il listener. Se un listener non serve più ma non è stato rimosso, continuerà a ricevere notifiche. Nelle applicazioni di lunga durata questo può portare a leak di memoria.

Errore n. 2: Operazioni lunghe o bloccanti nei gestori. Se il gestore esegue un lavoro pesante (I/O, DB), l’applicazione può «bloccarsi», soprattutto se le notifiche arrivano dal thread dell’UI. Spostate i lavori pesanti su thread in background.

Errore n. 3: Eccezioni nei listener. Un’eccezione in un listener può interrompere l’invio agli altri. Incapsulate le chiamate dei listener in try-catch e registrate gli errori nei log.

Errore n. 4: Registrazione multipla dello stesso listener. Se lo stesso listener viene aggiunto più volte, riceverà l’evento un numero duplicato di volte. Controllate la registrazione e prevenite gli inserimenti duplicati.

Errore n. 5: Accoppiamento rigido tra osservato e osservatore. Se l’osservato conosce implementazioni specifiche degli osservatori, si viola il loose coupling. Usate solo interfacce (ad esempio, TemperatureObserver, CounterListener).

Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION