Padrão de Projeto Observer em Java

Este artigo demonstra a evolução de um design até chegar ao padrão Observer em Java, utilizando um cenário prático para ilustrar os conceitso.

Cenário Inicial

Considere o seguinte cenário: um sensor de temperatura monitora um ambiente e, quando a temperatura ultrapassa um limite, um sistema de resfriamento deve ser ativado.

Primeira Abordagem - Polling

A implementação mais simples utiliza verificação contínua (polling) para monitorar o estado do sensor:

class TemperatureSensor {
    private double currentTemp = 20.0;
    private final double threshold = 30.0;

    public void simulateTemperatureRise() {
        currentTemp = 35.0;
    }

    public boolean isThresholdExceeded() {
        return currentTemp > threshold;
    }

    public double getCurrentTemp() {
        return currentTemp;
    }
}

class CoolingSystem implements Runnable {
    private final TemperatureSensor sensor;

    public CoolingSystem(TemperatureSensor sensor) {
        this.sensor = sensor;
    }

    @Override
    public void run() {
        while (!sensor.isThresholdExceeded()) {
            System.out.println("Temperatura normal: " + sensor.getCurrentTemp() + "°C");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        activateCooling();
    }

    private void activateCooling() {
        System.out.println("Sistema de resfriamento ativado!");
    }
}

public class SensorDemo {
    public static void main(String[] args) {
        TemperatureSensor sensor = new TemperatureSensor();
        Thread monitoringThread = new Thread(new CoolingSystem(sensor));
        monitoringThread.start();

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        sensor.simulateTemperatureRise();
    }
}

Este código funciona, mas apresenta um problema sério de eficiência: o sistema de resfriamento fica constantemente verificando o sensor, desperdiçando recursos de CPU. Se a temperatura nunca subisse, o loop rodaria indefinidamente.

Abordagem com Notificação Direta

Uma melhoria consiste em inverter a responsabilidade: em vez do sistema de resfriamento verificar o sensor, o sensor notifica diretamente o sistema quando algo acontece.

class TemperatureSensor implements Runnable {
    private CoolingSystem cooling;
    private double currentTemp = 20.0;

    public TemperatureSensor(CoolingSystem cooling) {
        this.cooling = cooling;
    }

    public void thresholdReached() {
        cooling.activate("setor-A");
    }

    @Override
    public void run() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        currentTemp = 35.0;
        thresholdReached();
    }
}

class CoolingSystem {
    public void activate(String zone) {
        System.out.println("Resfriamento ativado na zona: " + zone);
    }
}

public class SensorDemo {
    public static void main(String[] args) {
        CoolingSystem cooler = new CoolingSystem();
        TemperatureSensor sensor = new TemperatureSensor(cooler);
        new Thread(sensor).start();
    }
}

Após 3 segundos, a saída será:

Resfriamento ativado na zona: setor-A

Esta abordagem é mais eficiente, porém carece de flexibilidade. O sensor está diretamente acoplado ao sistema de resfriamento. E se outros componentes precisassem reagir ao evento?

Introduzindo Eventos

Para tornar o design mais robusto, criamos uma classe de evento que encapsula todas as informações relevantes:

class ThresholdEvent {
    private final long timestamp;
    private final String zone;
    private final double temperature;
    private final Object source;

    public ThresholdEvent(long timestamp, String zone, double temperature, Object source) {
        this.timestamp = timestamp;
        this.zone = zone;
        this.temperature = temperature;
        this.source = source;
    }

    public long getTimestamp() { return timestamp; }
    public String getZone() { return zone; }
    public double getTemperature() { return temperature; }
    public Object getSource() { return source; }
}

class TemperatureSensor implements Runnable {
    private CoolingSystem cooling;
    private double currentTemp = 20.0;

    public TemperatureSensor(CoolingSystem cooling) {
        this.cooling = cooling;
    }

    private void notifyThresholdReached() {
        ThresholdEvent event = new ThresholdEvent(
            System.currentTimeMillis(), "setor-A", currentTemp, this
        );
        cooling.onThresholdReached(event);
    }

    @Override
    public void run() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        currentTemp = 35.0;
        notifyThresholdReached();
    }
}

class CoolingSystem {
    public void onThresholdReached(ThresholdEvent event) {
        System.out.println("Zona: " + event.getZone());
        System.out.println("Temperatura registrada: " + event.getTemperature() + "°C");
        System.out.println("Resfriamento ativado!");
    }
}

Agora o evento carrega consigo todas as informações contextuais. Note que propriedades como zona e temperatura pertencem ao evento, não ao sensor — seguindo o princípio de que atributos devem estar na classe mais adequada.

Implementação com Padrão Observer

O problema anterior persiste: adicionar novos observadores (como um sistema de alarme ou um logger) exigiria modificar a classe do sensor repetidamente. O padrão Observer resolve isso elegante.

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

// Interface do observador
interface ThresholdListener {
    void onThresholdReached(ThresholdEvent event);
}

// Classe de evento
class ThresholdEvent {
    private final long timestamp;
    private final String zone;
    private final double temperature;
    private final Object source;

    public ThresholdEvent(long timestamp, String zone, double temperature, Object source) {
        this.timestamp = timestamp;
        this.zone = zone;
        this.temperature = temperature;
        this.source = source;
    }

    public long getTimestamp() { return timestamp; }
    public String getZone() { return zone; }
    public double getTemperature() { return temperature; }
    public Object getSource() { return source; }
}

// Sujeito observável
class TemperatureSensor implements Runnable {
    private final List<ThresholdListener> listeners = new ArrayList<>();
    private double currentTemp = 20.0;

    public void addListener(ThresholdListener listener) {
        listeners.add(listener);
    }

    public void removeListener(ThresholdListener listener) {
        listeners.remove(listener);
    }

    private void fireEvent() {
        ThresholdEvent event = new ThresholdEvent(
            System.currentTimeMillis(), "setor-A", currentTemp, this
        );
        for (ThresholdListener listener : listeners) {
            listener.onThresholdReached(event);
        }
    }

    @Override
    public void run() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        currentTemp = 35.0;
        fireEvent();
    }
}

// Observador: Sistema de resfriamento
class CoolingSystem implements ThresholdListener {
    @Override
    public void onThresholdReached(ThresholdEvent event) {
        System.out.println("[Resfriamento] Zona: " + event.getZone() 
            + " | Temp: " + event.getTemperature() + "°C");
        System.out.println("Ativando resfriamento...");
    }
}

// Observador: Sistema de alarme
class AlarmSystem implements ThresholdListener {
    @Override
    public void onThresholdReached(ThresholdEvent event) {
        System.out.println("[Alarme] ALERTA! Temperatura crítica em " 
            + event.getZone() + ": " + event.getTemperature() + "°C");
        System.out.println("Acionando sirene!");
    }
}

// Observador: Logger
class EventLogger implements ThresholdListener {
    @Override
    public void onThresholdReached(ThresholdEvent event) {
        System.out.println("[Log] Evento registrado às " + event.getTimestamp() 
            + " | Zona: " + event.getZone() + " | Temp: " + event.getTemperature() + "°C");
    }
}

// Demonstração
public class ObserverPatternDemo {
    public static void main(String[] args) {
        TemperatureSensor sensor = new TemperatureSensor();

        // Registrando múltiplos observadores
        sensor.addListener(new CoolingSystem());
        sensor.addListener(new AlarmSystem());
        sensor.addListener(new EventLogger());

        new Thread(sensor).start();
    }
}

Saída após 3 segundos:

[Resfriamento] Zona: setor-A | Temp: 35.0°C
Ativando resfriamento...
[Alarme] ALERTA! Temperatura crítica em setor-A: 35.0°C
Acionando sirene!
[Log] Evento registrado às 1623456789123 | Zona: setor-A | Temp: 35.0°C

Benefícios desta Implementação

  • Desacoplamento: O sensor não conhece as classes concretas dos observadores, apenas a interface ThresholdListener
  • Abertura para extensão: Novos observadores podem ser adiciondaos sem modificar o código do sensor
  • Princípio Open/Closed: O sistema está aberto para extensão mas fechado para modificação
  • Eficiência: Nenhum ciclo de polling desperdiçando recursos de CPU

O padrão Observer é fundamental em sistemas orientados a eventos e é amplamente utilizado em frameworks como Swing/AWT para interfaces gráficas, em sistemas de mensageria e em arquiteturas baseaads em eventos.

Tags: java observer-pattern design-patterns event-driven Interface

Publicado em 6-13 04:04 por Thomas