ComportamentalIntermediário7 min de leitura10 seções1994, Marco de referência: a ideia é mais velha que a data

Observer

Define uma dependência um-para-muitos: quando um objeto muda de estado, todos os seus dependentes são notificados automaticamente.

Em 10 segundos

Um objeto (o sujeito) mantém a lista de interessados e os notifica a cada mudança de estado. Quem emite não conhece quem consome — e observadores entram e saem em tempo de execução.

Antes de ter nome

O MVC do Smalltalk-80 (1979) já fazia a view se inscrever no model para ser notificada — o padrão tinha quinze anos quando ganhou nome.

  • #eventos
  • #gof
  • #pub-sub
  • #reatividade
Analogia

Como assinar uma newsletter

Você se inscreve uma vez. Quando sai uma edição nova, todos os assinantes recebem automaticamente — e o jornal não precisa saber quem é cada um. Cancelar a inscrição para de receber. É exatamente isso que o Observer faz com objetos.

01Conceito

O problema

Vários objetos precisam reagir quando outro muda de estado. Ficar checando em loop (polling) é caro e acopla todo mundo ao objeto observado.

E você quer poder adicionar ou remover interessados em tempo de execução, sem mexer no objeto que emite as mudanças.

A raiz do problema é a direção da dependência. Sem o padrão, ou os consumidores ficam perguntando 'mudou? mudou?' (polling: CPU gasta à toa e latência entre a mudança e a reação), ou o objeto observado chama cada dependente pelo nome — e aí qualquer consumidor novo exige editar quem emite, violando o Aberto/Fechado. Observer inverte: o emissor conhece apenas uma interface de notificação, e a lista de quem escuta vira dado, não código.

Há uma nuance clássica: push vs pull. No modelo push, o sujeito envia o estado junto da notificação (como nos exemplos abaixo); no pull, ele só avisa 'mudei' e cada observador busca o que precisa. Push é mais simples; pull evita mandar dado que ninguém usa e deixa cada observador ler só a fatia que lhe interessa.

Compare com pub/sub via broker: no Observer o sujeito guarda a lista e chama os observadores diretamente, tudo in-process e síncrono por padrão. No pub/sub um mediador (canal, event bus, broker) fica no meio, e emissor e consumidor nem sabem da existência um do outro — é o Observer levado à escala distribuída, com custos de infraestrutura e entrega que o padrão original não tem.

Como se encaixa
  1. Mudança de estado
  2. disparaSujeito
  3. percorre a listanotificar()
  4. atualizar(estado)Observadores
  5. reagemEfeitos (UI, log, cache)
O sujeito não conhece ninguém pelo nome — só percorre a lista e avisa. Cada observador decide sozinho o próprio efeito.
02Interativo

Veja funcionando

Mexa nos controles: a teoria acima vira comportamento observável.

Sujeito · preço = R$ 100

Gráfico

atualiza ponto → 100

Alerta e-mail

dispara e-mail (R$ 100)

Log/Auditoria

registra R$ 100

Um clique, uma notificação → os 3 observadores reagem sem o sujeito saber quem são.

Passo a passoO fluxo, passo a passo
  1. 1

    Inscrever

    Cada observador se registra no sujeito (entra na lista).

  2. 2

    Mudar estado

    Algo altera o estado do sujeito (ex.: novo preço).

  3. 3

    Notificar

    O sujeito percorre a lista e chama atualizar() em cada um.

  4. 4

    Reagir

    Cada observador decide sozinho o que fazer com a novidade.

03Anatomia

Navegue pelas camadas

Camada 1/4Fonte de estado

Camada 1

Fonte de estado

onde a mudança acontece

É o dado que muda: um preço, o status de um pedido, o valor de um campo. A fonte não precisa saber que existe alguém interessado — ela só altera o estado do sujeito. Manter essa separação é o que permite reaproveitar o mesmo mecanismo para qualquer tipo de mudança.

No código

preco.notificar(42); // a fonte só empurra o novo estado

Se violar: se os interessados lerem a fonte diretamente (polling), voltam o acoplamento e o custo de ficar perguntando — exatamente o que o padrão elimina.

04Estrutura

Estrutura (diagrama de classes)

Diagrama de classes
05Implementação

Código

type Observador<T> = (estado: T) => void;

class Sujeito<T> {
  private observadores = new Set<Observador<T>>();
  inscrever(o: Observador<T>) { this.observadores.add(o); return () => this.observadores.delete(o); }
  notificar(estado: T) { this.observadores.forEach((o) => o(estado)); }
}

const preco = new Sujeito<number>();
const cancelar = preco.inscrever((p) => console.log("novo preço", p));
preco.notificar(42);  // "novo preço 42"
cancelar();
06No mundo real

Casos de uso

UI reativa: estado notifica componentes

Cenário

Numa SPA, o mesmo dado aparece em vários lugares — o contador do carrinho no header, a lista de itens e o resumo do checkout precisam refletir qualquer mudança na hora.

Como o padrão entra

O store é o sujeito; cada componente se inscreve no mount e re-renderiza ao ser notificado. É a base de Redux, Zustand, signals e do data-binding de frameworks reativos — você usa Observer todo dia sem escrever a classe Sujeito.

Trade-off: Notificar a cada mudança pode re-renderizar demais; na prática entram seletores e memoização para cada componente reagir só à fatia de estado que consome.

Notificações multi-canal

Cenário

Quando um pedido muda de status, o cliente deve receber e-mail, push e SMS — e o time quer adicionar WhatsApp sem mexer no fluxo do pedido.

Como o padrão entra

O serviço de pedidos emite 'StatusAlterado'; cada canal é um observador independente. Canal novo = observador novo, sem tocar em quem emite.

Trade-off: Observadores in-process não sobrevivem a um restart: se o processo cair no meio da notificação, os canais restantes ficam sem aviso. Quando a entrega precisa de garantia, o passo seguinte é uma fila/broker.

Invalidação de cache por evento

Cenário

Um cache acelera as leituras, mas serve dado velho quando a escrita acontece por outro caminho.

Como o padrão entra

Cada escrita notifica a mudança; um observador traduz o evento em chaves de cache a invalidar (ou atualiza o valor direto). O cache reage à mudança em vez de expirar no relógio.

Trade-off: O mapeamento evento→chaves é manual e frágil: invalidar demais derruba o hit rate, invalidar de menos serve dado desatualizado.

O Observer que vaza memória

Compila, roda, passa no code review. E quebra depois.

Todo mundo lembra de se inscrever; quase ninguém lembra de sair. Como o sujeito guarda uma referência forte para cada observador, o objeto que deveria morrer continua vivo — e continua sendo notificado.

class Sujeito {
  private observadores: ((v: number) => void)[] = [];

  inscrever(o: (v: number) => void) {
    this.observadores.push(o);   // <- e para sair, como?
  }

  notificar(v: number) {
    this.observadores.forEach((o) => o(v));
  }
}

class Painel {
  constructor(private sujeito: Sujeito) {
    // A closure captura 'this'. Enquanto o sujeito viver,
    // este Painel inteiro fica vivo junto.
    sujeito.inscrever((v) => this.desenhar(v));
  }
  desenhar(v: number) { /* ... */ }
}

// Cada navegacao cria um Painel. Nenhum e coletado.
// Depois de 200 telas, sao 200 Paineis recebendo notificacao.

O que quebra

  • Depois de horasA memória cresce sem teto e o processo é reiniciado pelo orquestrador — sem nenhum erro no log que aponte a causa.
  • Na navegaçãoComponentes já removidos da tela continuam reagindo e escrevendo em estado que não existe mais.
  • Ao notificarO custo de cada evento cresce com o tempo, porque a lista só aumenta.

A saída: inscrever devolve a função de cancelamento, e quem se inscreve é obrigado a chamá-la ao morrer — é o return () => ... do exemplo correto acima, e o cleanup do useEffect. Onde a linguagem permitir, referência fraca ajuda; disciplina de cancelamento resolve.

Do cheiro ao padrão

Um passo por vez, com o motivo de cada um.

O cheiro: A classe que muda o preço chamando, pelo nome, cada um dos quatro lugares que precisam reagir.

Como está. A classe que muda o preço chamando, pelo nome, cada um dos quatro lugares que precisam reagir.

class Precos {
  constructor(
    private readonly vitrine: Vitrine,
    private readonly cache: Cache,
    private readonly auditoria: Auditoria,
    private readonly emails: Emails,
  ) {}

  atualizar(sku: string, preco: number) {
    this.salvar(sku, preco);

    // Toda reacao nova = editar esta classe e este metodo.
    this.vitrine.redesenhar(sku);
    this.cache.invalidar(sku);
    this.auditoria.registrar(sku, preco);
    this.emails.avisarInteressados(sku, preco);
  }
}
09Cuidado

Armadilhas

  1. Vazamento de memória por não desinscrever

    O sujeito guarda referência para cada observador. Um componente destruído que nunca se desinscreveu continua na lista: segue sendo notificado, executando efeito sobre UI morta e impedindo o garbage collector de liberá-lo. Sempre guarde a função de cancelamento e chame-a no teardown (ou use AbortSignal / referências fracas).

  2. Assumir ordem de notificação

    O padrão não garante em que ordem os observadores são chamados. Código que depende de 'o observador A roda antes do B' funciona por acidente e quebra na primeira refatoração da lista. Se a ordem importa, torne-a explícita — prioridades, pipeline de handlers — ou repense se Observer é o padrão certo.

  3. Cascatas evento→evento→evento

    Um observador que altera o estado de outro sujeito dispara nova rodada de notificações. Três níveis depois, ninguém sabe por que a tela re-renderizou cinco vezes — e um ciclo entre dois sujeitos trava tudo. Mantenha observadores sem efeitos que emitam eventos, agrupe mudanças (batching) e monitore a profundidade das cadeias.

10Decisão

Quando usar × quando evitar

Use quando

3
  • Mudança em um objeto exige atualizar outros, e você não sabe quantos nem quais em tempo de compilação.
  • Você quer baixo acoplamento entre quem emite e quem consome eventos.
  • Base de sistemas reativos, UIs data-binding e filas de eventos.

Evite quando

2
  • A ordem de notificação importa e precisa ser garantida — Observer não define ordem.
  • Cadeias longas de notificação podem virar cascatas difíceis de depurar.

Testar o que ficou

3 armadilhas de Observer viram perguntas, com o nome do padrão escondido, misturadas com padrões parecidos.

Onde isto aparece de verdade

Você já usa isto, talvez sem saber o nome.

  • addEventListenerVocê registra interesse e é chamado quando acontece — sem perguntar de tempos em tempos.
  • EventEmitter do Nodeon e emit são inscrição e notificação, com a lista de ouvintes por dentro.
  • RxJSObservable e subscriber levam o padrão ao extremo, com composição de fluxos por cima.
  • Signals de Vue e SolidA dependência é registrada na leitura, e a escrita notifica quem leu.

O padrão em uma linha

Sem nome de domínio, sem cerimônia. Só a ideia.

// Inscrever devolve o cancelamento. Sem isso, vaza.
const cancelar = sujeito.inscrever((estado) => reagir(estado));

O que ele cobra

1 salto a mais entre a chamada e o efeito.

  • A ordem de notificação deixa de ser garantida
  • O rastro de execução some: quem disparou o efeito não aparece na pilha
  • Todo inscrito precisa lembrar de cancelar

Não vale se: há um único interessado e ele é conhecido em tempo de compilação. Aí é uma chamada de função.

Costuma ser confundido com

Onde isto aparece

Continue por aqui