GoF, p. 293 também chamado de Publish-Subscribe, Dependents
Observador
Observer
Um objeto avisa automaticamente todos os interessados quando algo muda.
Definir uma dependência um-para-muitos entre objetos, de modo que quando um objeto muda de estado, todos os seus dependentes são notificados e atualizados automaticamente.
A assinatura da revista
Você não vai à banca todo dia perguntar se a edição saiu — você assina. Quando a revista é publicada, ela chega a todos os assinantes. A editora não conhece você pessoalmente, só tem uma lista. E você pode cancelar a assinatura quando quiser, sem afetar os outros.
Objetos precisam reagir a mudanças em outro objeto. As duas soluções ingênuas são ruins: ficar consultando em laço (polling) desperdiça recursos, ou o objeto observado conhecer cada interessado, o que o acopla a tudo e exige alterá-lo a cada novo interessado.
O objeto observado (Subject) mantém uma lista de assinantes e expõe métodos para entrar e sair dela. Quando muda, percorre a lista chamando o mesmo método de notificação em cada um. Ele conhece apenas a interface Observer.
- Uma função de “salvar” que também manda e-mail, atualiza cache e loga.
- Laços de polling verificando se algo mudou.
Estrutura
- Subject / Publisher
- Mantém a lista de observers e notifica todos nas mudanças.
- Observer / Subscriber
- Interface com o método de atualização (`atualizar(evento)`).
- ConcreteObserver
- Reage à notificação do jeito que lhe convém.
Implementação
// ── Publisher: sabe QUE algo aconteceu, não QUEM se importa ──
class Publicador {
#assinantes = new Map(); // evento → Set de handlers
inscrever(evento, handler) {
if (!this.#assinantes.has(evento)) this.#assinantes.set(evento, new Set());
this.#assinantes.get(evento).add(handler);
return () => this.cancelar(evento, handler); // devolve o "unsubscribe"
}
cancelar(evento, handler) { this.#assinantes.get(evento)?.delete(handler); }
notificar(evento, dados) {
for (const h of this.#assinantes.get(evento) ?? []) {
// Um observer que falha não pode derrubar os outros.
try { h(dados); } catch (e) { console.error('observer falhou:', e.message); }
}
}
}
// ── Subject concreto: a regra de negócio ──────────────
class Pedido extends Publicador {
constructor(id, total) { super(); this.id = id; this.total = total; this.status = 'novo'; }
confirmar() {
this.status = 'confirmado';
this.notificar('confirmado', { id: this.id, total: this.total });
}
}
// ── Observers: cada um cuida do seu assunto ───────────
const enviarEmail = p => console.log(`✉️ confirmação do pedido ${p.id}`);
const atualizarBI = p => console.log(`📊 +R$ ${p.total} no painel`);
const auditar = p => console.log(`📝 log: pedido ${p.id} confirmado`);
const emitirNota = p => { throw new Error('SEFAZ fora do ar'); };
const pedido = new Pedido('p_42', 250);
pedido.inscrever('confirmado', enviarEmail);
pedido.inscrever('confirmado', atualizarBI);
const desinscrever = pedido.inscrever('confirmado', auditar);
pedido.inscrever('confirmado', emitirNota);
pedido.confirmar();
// ✉️ … 📊 … 📝 … observer falhou: SEFAZ fora do ar ← os demais rodaram
desinscrever(); // sai da lista sem afetar ninguém
// Adicionar uma nova reação NÃO exige tocar na classe Pedido. - java.util.EventListener e todo o Swing/AWT
- addEventListener no DOM; EventEmitter do Node.js
- RxJS, sinais reativos (signals), Vue reactivity, MobX
Consequências
- Novos assinantes entram sem alterar o publicador (Aberto/Fechado).
- Relações estabelecidas e desfeitas em tempo de execução.
- Publicador e assinantes ficam fracamente acoplados.
- A ordem de notificação é, em geral, indefinida — não confie nela.
- Vazamento de memória clássico: o “lapsed listener” (observer não removido continua referenciado).
- Cascatas de notificação são difíceis de depurar; o fluxo some do código.
- Mudanças em um objeto exigem mudar outros, e você não sabe quais nem quantos de antemão.
- Alguns objetos precisam observar outros apenas por um tempo limitado.
- Você quer separar o núcleo de negócio de efeitos colaterais (e-mail, métricas, cache).
- Existe um único dependente fixo e conhecido — uma chamada direta é mais legível.
- A ordem e a atomicidade das reações são críticas.
Relações com outros padrões
Verificação
Qual é a armadilha de memória mais comum com Observer?
- O “lapsed listener”: um observer que nunca cancela a inscrição segue referenciado e nunca é coletado
- O publicador duplicar a lista de assinantes a cada notificação
- Observers criarem cópias profundas dos eventos
- A fila de eventos crescer indefinidamente
Sempre guarde e chame o “unsubscribe” — no ciclo de vida do componente, no `finally`, ou usando referências fracas.