Pular para o conteúdo
GoF Padrões de Projeto Comportamentais
Comportamental escopo de objeto
GoF, p. 293 também chamado de Publish-Subscribe, Dependents

Observador

Observer

Um objeto avisa automaticamente todos os interessados quando algo muda.

Complexidade média Frequência de uso muito comum
Intençãocomo o livro define

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.

Analogiauma imagem do mundo real

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.

O problema

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.

A solução

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.

Sintomascomo reconhecer no seu código
  • Uma função de “salvar” que também manda e-mail, atualiza cache e loga.
  • Laços de polling verificando se algo mudou.

Estrutura

notifica 0..*Publisher- assinantes[]+ inscrever(o)+ notificar(ev)«interface»Observer+ atualizar(ev)PainelDeVendasEnvioDeEmailAuditoria
Fig. 19 — Observer herda / implementa cria usa / contém
Ver este diagrama sendo desenhado, traço a traço
Participantes
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

Listagem 19 Um pedido confirmado dispara reações independentes
// ── 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.
Na práticaonde ele já existe
  • java.util.EventListener e todo o Swing/AWT
  • addEventListener no DOM; EventEmitter do Node.js
  • RxJS, sinais reativos (signals), Vue reactivity, MobX

Consequências

A favor
  • 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.
Contra
  • 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.
Use quando
  • 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).
Evite quando
  • 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

costuma andar junto
Mediator
Mediator é frequentemente implementado com Observer; a intenção difere.
Command
Assinantes podem ser comandos enfileirados.
Singleton
Event buses globais costumam ser singletons.
Memento
Eventos podem carregar snapshots do estado anterior.

Verificação

Questãoa resposta está marcada

Qual é a armadilha de memória mais comum com Observer?

  1. O “lapsed listener”: um observer que nunca cancela a inscrição segue referenciado e nunca é coletado
  2. O publicador duplicar a lista de assinantes a cada notificação
  3. Observers criarem cópias profundas dos eventos
  4. 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.