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.
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.
- Mudança de estado
- disparaSujeito
- percorre a listanotificar()
- atualizar(estado)Observadores
- reagemEfeitos (UI, log, cache)
Veja funcionando
Mexa nos controles: a teoria acima vira comportamento observável.
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.
- 1
Inscrever
Cada observador se registra no sujeito (entra na lista).
- 2
Mudar estado
Algo altera o estado do sujeito (ex.: novo preço).
- 3
Notificar
O sujeito percorre a lista e chama atualizar() em cada um.
- 4
Reagir
Cada observador decide sozinho o que fazer com a novidade.
Navegue pelas camadas
Camada 1/4Fonte de estado
use ← →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 estadoSe 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.
Estrutura (diagrama de classes)
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();class Sujeito:
def __init__(self):
self._observadores = []
def inscrever(self, cb):
self._observadores.append(cb)
return lambda: self._observadores.remove(cb)
def notificar(self, estado):
for cb in list(self._observadores):
cb(estado)
preco = Sujeito()
cancelar = preco.inscrever(lambda p: print("novo preço", p))
preco.notificar(42) # novo preço 42
cancelar()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);
}
}Armadilhas
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).
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.
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.
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 Nodeoneemitsã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.