1. Conhecendo o padrão “Observer”
O padrão “Observer” — um dos mais conhecidos e fundamentais padrões de projeto. Ele descreve uma situação em que um objeto (observável, ou subject) informa sobre suas mudanças a outros objetos (observadores), que se inscreveram para receber essas mudanças.
Falando de forma simples: temos um canal do Telegram (objeto observável) e “assinantes” (observadores). Sempre que sai um novo post, o canal notifica todos os assinantes, e eles decidem o que fazer — ler, ignorar ou cancelar a inscrição.
Na programação, esse padrão permite notificar automaticamente objetos interessados sobre eventos ou mudanças de estado, sem acoplá-los diretamente entre si. Isso é importante para construir sistemas flexíveis, extensíveis e fáceis de manter.
Onde o padrão “Observer” aparece?
- Em interfaces gráficas (Swing, AWT, JavaFX) — ouvintes de eventos.
- Em bibliotecas reativas (RxJava, Project Reactor).
- Na lógica de negócios: reagir à mudança de estado do modelo.
- Em engines de jogos (eventos de colisão, vitória, derrota etc.).
- Em qualquer lugar onde seja preciso separar “o que aconteceu” de “o que fazer com isso”.
Relação do padrão com eventos e ouvintes no Java
De fato, todo o modelo de eventos do Java é construído sobre o “Observer”. Quando você escreve button.addActionListener(listener);, você está implementando esse padrão:
- Observável — o botão (ou outro componente).
- Observador — seu ouvinte, que implementa o método actionPerformed().
- Evento — o usuário clicou, passou o mouse etc.
- Notificação — o componente chama actionPerformed().
Tudo isso é uma implementação clássica de Observer!
2. Implementação clássica do padrão “Observer”
Vamos ver como implementar o padrão em suas próprias classes — sem Swing e AWT, para perceber que não há mágica.
Elementos principais do padrão
- Observable (Subject) — objeto observável. Armazena a lista de observadores e os notifica sobre mudanças.
- Observer — interface do observador, geralmente com o método update().
Exemplo: Termômetro e ar‑condicionado
Interface do observador
public interface TemperatureObserver {
void temperatureChanged(int newTemperature);
}
Classe “Termômetro” (observável)
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);
}
}
}
Exemplo de observador — “Ar‑condicionado”
public class AirConditioner implements TemperatureObserver {
@Override
public void temperatureChanged(int newTemperature) {
if (newTemperature > 25) {
System.out.println("Ar-condicionado ligado! Está quente: " + newTemperature + "°C");
} else {
System.out.println("Ar-condicionado desligado. 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); // Ar-condicionado desligado. Temperatura: 22°C
thermometer.setTemperature(28); // Ar-condicionado ligado! Está quente: 28°C
}
}
Eis toda a mágica! Você pode adicionar mais cem observadores — todos receberão notificações quando a temperatura mudar.
Diagrama do padrão
flowchart LR
T["Termômetro (Observable)"] -- notifica --> AC["Ar-condicionado (Observer)"]
T -- notifica --> L["Logger (Observer)"]
T -- notifica --> Alarm["Alarme (Observer)"]
Detalhes modernos: Observable obsoleto e novas abordagens
Na biblioteca padrão do Java existiam java.util.Observable e java.util.Observer, mas desde o Java 9 foram marcados como obsoletos (deprecated). O motivo — falta de flexibilidade (por exemplo, Observable é uma classe, não uma interface, o que dificulta herdar de outra classe).
A abordagem moderna é projetar suas próprias interfaces de ouvintes e a lógica de inscrição/cancelamento (como no exemplo acima). Isso é mais flexível, seguro e se ajusta melhor às tarefas reais.
3. Exemplo: miniaplicativo com assinantes
Vamos fazer um “contador de cliques” com a possibilidade de assinar a mudança do valor.
Interface do ouvinte
public interface CounterListener {
void counterChanged(int newValue);
}
Classe contador
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;
}
}
Ouvinte: imprimir mensagem
public class ConsoleCounterListener implements CounterListener {
@Override
public void counterChanged(int newValue) {
System.out.println("O contador mudou: " + newValue);
}
}
Uso
public class Main {
public static void main(String[] args) {
Counter counter = new Counter();
counter.addCounterListener(new ConsoleCounterListener());
counter.increment(); // O contador mudou: 1
counter.increment(); // O contador mudou: 2
}
}
4. Detalhes úteis
Alternativas e extensões modernas
Em projetos reais, muitas vezes se usam classes anônimas ou expressões lambda para se inscrever: counter.addCounterListener(newValue -> System.out.println("Novo valor: " + newValue));
(Para fazer isso, a interface deve ser funcional — ter um único método abstrato.)
Também são populares as bibliotecas reativas (RxJava, Project Reactor), onde o “Observer” é implementado com suporte a fluxos de eventos, filtragem, assincronia etc. Para entender a essência, basta o esquema clássico analisado acima.
Aplicações do padrão “Observer” no dia a dia
- Modelos de dados. Mudanças no modelo (lista de tarefas, produtos, usuários) notificam as visualizações para atualização.
- Registro (logging). Um assinante de log reage a eventos em todo o sistema.
- Notificações. Ao mudar o estado — envio de email, notificações push, mensagens no Telegram.
- Jogos. Mudança de saúde, aparecimento de inimigos, conclusão de nível.
- Várias threads. Uma thread publica eventos, outras reagem.
5. Erros comuns ao implementar o padrão “Observer”
Erro nº 1: Esqueceu de remover o ouvinte. Se o ouvinte não é mais necessário, mas não foi removido, ele continuará recebendo notificações. Em aplicações de longa duração isso pode levar a vazamentos de memória.
Erro nº 2: Operações longas ou bloqueantes nos handlers. Se o handler executa trabalho pesado (E/S, BD), o aplicativo pode “travar”, especialmente se as notificações vêm da thread de UI. Transfira tarefas pesadas para threads em segundo plano.
Erro nº 3: Exceções nos ouvintes. Uma exceção em um ouvinte pode interromper o envio aos demais. Encapsule as chamadas dos ouvintes em try-catch e registre os erros em log.
Erro nº 4: Registro múltiplo do mesmo ouvinte. Se um ouvinte for adicionado várias vezes, ele receberá o evento o mesmo número de vezes. Controle o registro e aplique proteção contra adição duplicada.
Erro nº 5: Forte acoplamento entre observável e observador. Se o observável conhece implementações concretas dos observadores, a fraca dependência é rompida. Use apenas interfaces (por exemplo, TemperatureObserver, CounterListener).
GO TO FULL VERSION