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.