Padrão Comportamental (GoF)

Observer

Define uma dependência um-para-muitos entre objetos: quando o sujeito muda de estado, todos os seus observadores são notificados e atualizados automaticamente — sem que o sujeito precise conhecer cada um deles.

Intenção

Estabelecer uma relação de dependência um-para-muitos de forma que, quando o sujeito (Subject / Observable) altera seu estado interno, todos os observadores registrados são notificados automaticamente. O sujeito não conhece os tipos concretos dos observadores — ele se comunica somente pela interface Observer.

Catalogado pelo GoF (1994) como padrão comportamental, o Observer é a base conceitual de sistemas de eventos, data-binding em frameworks de UI (React, Vue, Angular), streams reativos (RxJS, ReactiveX) e qualquer mecanismo de notificação onde o produtor de dados não deve acoplamento direto aos seus consumidores.

Problema

Imagine uma estação meteorológica que mede temperatura, umidade e pressão. Múltiplos displays precisam exibir esses dados: um painel atual, um gráfico histórico e um display de alertas. A abordagem ingênua acoplaria a estação a cada display diretamente:

// Abordagem ingênua — NÃO faça isso:
class EstacaoMeteorologica {
  private painelAtual: PainelAtual;
  private graficoHistorico: GraficoHistorico;
  private displayAlertas: DisplayAlertas;

  atualizar(temp: number, umid: number, pressao: number): void {
    this.painelAtual.mostrar(temp, umid, pressao);
    this.graficoHistorico.registrar(temp, umid, pressao);
    this.displayAlertas.verificar(temp, umid, pressao);
    // Cada novo display exige modificar esta classe.
  }
}

Adicionar ou remover um display exige modificar EstacaoMeteorologica — violação do Princípio Aberto/Fechado. Os displays também não podem ser registrados ou removidos em runtime. O Observer resolve ambos os problemas: a estação não conhece os displays, apenas publica mudanças; os displays se registram e cancelam inscrição de forma independente.

Solução

O Observer organiza o código em três participantes:

  1. Subject (Observable): mantém uma lista de observadores e oferece métodos para registrá-los (subscribe) e removê-los (unsubscribe). Quando seu estado muda, chama notify para percorrer a lista e avisar cada observador.
  2. Observer (interface): declara o método de atualização que o Subject chama. Ex.: update(data).
  3. ConcreteObserver: implementa Observer e reage à notificação conforme sua responsabilidade — exibir dados, registrar em log, enviar alerta.

Push vs Pull

Existem duas abordagens para o que o Subject envia na notificação:

  • Push: o Subject envia os dados atuais diretamente no argumento da notificação — update(temp, umid, pressao). É mais direto, mas acopla a assinatura do Observer ao formato exato dos dados do Subject. Quando o estado crescer, a interface do Observer muda.
  • Pull: o Subject notifica sem dados (ou com uma referência a si mesmo) — update(subject). Cada Observer pede ao Subject apenas o que precisa. Mais flexível: novos dados podem ser adicionados ao Subject sem quebrar a assinatura do Observer. O custo é que o Observer precisa conhecer a interface do Subject para fazer o pull.

Observer vs Pub/Sub

Observer e Publish/Subscribe são frequentemente confundidos, mas diferem no acoplamento:

  • Observer: o Subject conhece diretamente os seus Observers (mantém a lista). Há acoplamento direto Subject ↔ Observer, mas não entre Observers.
  • Pub/Sub: introduz um broker (barramento de eventos, event bus) entre publicadores e assinantes. Publicadores e assinantes não se conhecem — comunicam-se apenas pelo canal/tópico. O desacoplamento é total, mas a rastreabilidade do fluxo é menor.

Use Observer quando o número de observadores é gerenciável e a relação Subject–Observer é direta. Use Pub/Sub quando a comunicação precisa cruzar módulos ou serviços sem acoplamento direto.

Estrutura

         «interface»
           Observer
    ┌──────────────────────────┐
    │ + update(data: T): void  │
    └──────────────────────────┘
              ▲
   ┌──────────┴─────────────────────┐
   │                                │
PainelAtual               GraficoHistorico
(ConcreteObserver)        (ConcreteObserver)


           Subject
  ┌───────────────────────────────────────────┐
  │ - observers: Observer[]                   │
  │ + subscribe(o: Observer): void            │
  │ + unsubscribe(o: Observer): void          │
  │ # notify(): void                          │
  └───────────────────────────────────────────┘
              ▲
  EstacaoMeteorologica (ConcreteSubject)
  ┌───────────────────────────────────────────┐
  │ - temperatura: number                     │
  │ - umidade: number                         │
  │ + setMedicao(t, u): void                  │
  │   → chama this.notify()                   │
  └───────────────────────────────────────────┘


Fluxo de notificação (modelo push):

  estacao.setMedicao(25, 60)
    → notify()
      → painel.update({ temperatura: 25, umidade: 60 })
      → grafico.update({ temperatura: 25, umidade: 60 })
      → alertas.update({ temperatura: 25, umidade: 60 })

Exemplos de código

Exemplo 1 — Estação meteorológica com múltiplos displays

Implementação completa com Subject genérico, registro/cancelamento de inscrição e notificação. Usa modelo push com um objeto de dados tipado.

// ── Interfaces ────────────────────────────────────────────────
interface DadosMeteo {
  temperatura: number;
  umidade: number;
}

interface Observer<T> {
  update(data: T): void;
}

// ── Subject genérico ──────────────────────────────────────────
class Subject<T> {
  private readonly observers: Set<Observer<T>> = new Set();

  subscribe(observer: Observer<T>): void {
    this.observers.add(observer);
  }

  unsubscribe(observer: Observer<T>): void {
    this.observers.delete(observer);
  }

  protected notify(data: T): void {
    for (const observer of this.observers) {
      observer.update(data);
    }
  }
}

// ── ConcreteSubject ───────────────────────────────────────────
class EstacaoMeteorologica extends Subject<DadosMeteo> {
  private temperatura = 0;
  private umidade = 0;

  setMedicao(temperatura: number, umidade: number): void {
    this.temperatura = temperatura;
    this.umidade = umidade;
    this.notify({ temperatura: this.temperatura, umidade: this.umidade });
  }
}

// ── ConcreteObservers ─────────────────────────────────────────
class PainelAtual implements Observer<DadosMeteo> {
  update(data: DadosMeteo): void {
    console.log(
      `[Painel] Temp: ${data.temperatura}C  Umidade: ${data.umidade}%`
    );
  }
}

class DisplayAlertas implements Observer<DadosMeteo> {
  update(data: DadosMeteo): void {
    if (data.temperatura > 35) {
      console.log("[ALERTA] Temperatura critica:", data.temperatura + "C");
    }
    if (data.umidade < 30) {
      console.log("[ALERTA] Umidade baixa:", data.umidade + "%");
    }
  }
}

class GraficoHistorico implements Observer<DadosMeteo> {
  private readonly historico: DadosMeteo[] = [];

  update(data: DadosMeteo): void {
    this.historico.push(data);
    console.log(`[Grafico] Registros: ${this.historico.length}`);
  }

  getDados(): readonly DadosMeteo[] {
    return this.historico;
  }
}

// ── Uso ──────────────────────────────────────────────────────
const estacao = new EstacaoMeteorologica();
const painel = new PainelAtual();
const alertas = new DisplayAlertas();
const grafico = new GraficoHistorico();

estacao.subscribe(painel);
estacao.subscribe(alertas);
estacao.subscribe(grafico);

estacao.setMedicao(25, 60);
// [Painel] Temp: 25C  Umidade: 60%
// [Grafico] Registros: 1

estacao.setMedicao(38, 25);
// [Painel] Temp: 38C  Umidade: 25%
// [ALERTA] Temperatura critica: 38C
// [ALERTA] Umidade baixa: 25%
// [Grafico] Registros: 2

// Cancelar inscrição do painel — não recebe mais notificações.
estacao.unsubscribe(painel);
estacao.setMedicao(20, 70);
// [Grafico] Registros: 3  (painel não foi notificado)

Exemplo 2 — Sistema de eventos tipado (modelo pull)

No modelo pull, o Subject notifica apenas que algo mudou (passando uma referência a si mesmo). Cada Observer consulta os dados que precisa. Este padrão é mais flexível quando o Subject tem estado rico e diferentes Observers precisam de subconjuntos distintos de informação.

// Modelo pull: o Observer recebe referência ao Subject e
// "puxa" apenas os dados que precisa.

interface PedidoObserver {
  onPedidoAtualizado(pedido: Pedido): void;
}

class Pedido {
  private readonly observers: Set<PedidoObserver> = new Set();

  private _status: string = "rascunho";
  private _itens: string[] = [];

  subscribe(o: PedidoObserver): void    { this.observers.add(o); }
  unsubscribe(o: PedidoObserver): void  { this.observers.delete(o); }

  get status(): string            { return this._status; }
  get itens(): readonly string[]  { return this._itens; }
  get total(): number             { return this._itens.length * 50; } // R$50/item (simplificado)

  adicionarItem(item: string): void {
    this._itens.push(item);
    this.notificar();
  }

  avancarStatus(novoStatus: string): void {
    this._status = novoStatus;
    this.notificar();
  }

  private notificar(): void {
    for (const o of this.observers) {
      o.onPedidoAtualizado(this); // passa `this` — o Observer puxa o que precisar
    }
  }
}

// Observer que só liga para o status:
class LogDeStatus implements PedidoObserver {
  onPedidoAtualizado(pedido: Pedido): void {
    console.log(`[Log] Status atual: ${pedido.status}`);
  }
}

// Observer que só liga para o total:
class CalculadoraTotais implements PedidoObserver {
  onPedidoAtualizado(pedido: Pedido): void {
    console.log(`[Total] R$${pedido.total.toFixed(2)} (${pedido.itens.length} itens)`);
  }
}

// ── Uso ──────────────────────────────────────────────────────
const pedido = new Pedido();
pedido.subscribe(new LogDeStatus());
pedido.subscribe(new CalculadoraTotais());

pedido.adicionarItem("Teclado");
// [Log] Status atual: rascunho
// [Total] R$50.00 (1 itens)

pedido.adicionarItem("Mouse");
// [Log] Status atual: rascunho
// [Total] R$100.00 (2 itens)

pedido.avancarStatus("confirmado");
// [Log] Status atual: confirmado
// [Total] R$100.00 (2 itens)

Quando usar

  • Quando uma mudança em um objeto exige atualizar outros e você não sabe quantos ou quais serão em tempo de projeto. Sistemas de notificação, feeds de dados ao vivo, pipelines de processamento.
  • Quando os observadores precisam ser registrados e removidos dinamicamente em runtime — módulos que entram e saem do sistema sem exigir recompilação.
  • Para implementar MVC/MVP: o Model é o Subject; Views são Observers que se atualizam automaticamente quando o Model muda. É a base de data-binding em Angular, Vue e similares.
  • Quando você quer desacoplar o produtor de dados de seus consumidores sem introduzir um broker intermediário (use Pub/Sub para isso).

Quando evitar

  • Quando a ordem de notificação importa e é crítica: o Observer não garante ordem de notificação por padrão. Se o Observer B depende de que o Observer A já processou a notificação, o padrão puro não garante isso — é necessária lógica adicional.
  • Quando os observadores são poucos, fixos e conhecidos: uma chamada direta é mais simples e rastreável do que montar a infra do Observer.
  • Quando notificações em cascata são prováveis: se um Observer modifica o Subject durante o update, disparando novas notificações, o sistema pode entrar em loops ou comportamentos imprevisíveis.

Prós e contras

Prós

  • Desacopla o Subject dos seus Observers — o Subject não precisa conhecer os tipos concretos.
  • Observadores podem ser adicionados e removidos em runtime sem alterar o Subject.
  • Segue o Princípio Aberto/Fechado — novos Observers não exigem modificar o Subject.
  • Base natural para sistemas reativos, data-binding e event-driven architecture.

Contras

  • Vazamento de memória: Observers não removidos mantêm o Subject (e si mesmos) vivos — armadilha clássica em linguagens com GC.
  • Ordem de notificação indeterminada: dependências entre Observers são difíceis de gerenciar.
  • Atualizações em cascata: um Observer que modifica o Subject pode disparar notificações inesperadas.
  • Difícil rastrear o fluxo de dados: com muitos Observers, entender "quem disparou o quê" requer ferramentas de debug específicas.

Armadilhas comuns

1. Vazamento de memória por não cancelar inscrição

A armadilha mais frequente: registrar um Observer e esquecer de chamar unsubscribe quando ele não é mais necessário. Enquanto o Subject existir, ele mantém uma referência forte ao Observer — impedindo que o GC colete o objeto. Em ambientes de longa vida (servidores, SPAs), o acúmulo de Observers "mortos" causa vazamento de memória.

Solução: use Set (facilita remoção por referência), retorne uma função de cancelamento (disposable) no subscribe, ou use WeakRef para Observers opcionais. Em TypeScript com RxJS, use takeUntil e unsubscribe no ciclo de vida do componente.

2. Atualizações em cascata

Se um Observer chama um método do Subject que dispara outra rodada de notificações, o resultado pode ser um loop infinito ou uma ordem de atualização imprevisível. A regra prática: Observers não devem modificar o estado do Subject durante o update. Se precisar, use um mecanismo de fila de eventos (next tick, microtask, setTimeout) para processar a mudança fora do ciclo atual de notificação.

3. Notificações excessivas

Um Subject que notifica a cada pequena mudança (ex.: cada keystroke em um campo de texto) pode sobrecarregar Observers lentos. Técnicas de mitigação: debounce (agrupa mudanças em um intervalo de tempo), dirty flag (notifica só se o valor realmente mudou) ou notificação em lote (acumula mudanças e notifica uma vez no final do ciclo).

4. Confundir Observer com Pub/Sub

Observer implica conhecimento direto: o Subject mantém a lista de Observers e os chama diretamente. Pub/Sub usa um broker intermediário (EventEmitter, Message Bus) que desacopla completamente publicadores de assinantes. Use Observer quando o acoplamento direto é aceitável; use Pub/Sub quando precisar de desacoplamento entre módulos ou serviços independentes.

Padrões relacionados

O Observer se relaciona com outros padrões comportamentais e estruturais:

O Strategy e o Observer compartilham o uso de interfaces para desacoplamento, mas com propósitos diferentes: Strategy troca algoritmos no contexto; Observer distribui notificações de estado para múltiplos receptores. O State também gerencia notificações implícitas quando o objeto muda de estado — em algumas implementações, o Estado notifica um Observer ou usa um Subject para propagar a mudança. O Mediator centraliza a comunicação entre objetos, eliminando referências diretas entre eles — é uma alternativa ao Observer quando muitos objetos precisam se comunicar de forma bidirecional, pois o Observer ainda acopla o Subject aos seus Observers pela lista de registros.