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:
-
Subject (Observable): mantém uma lista de observadores
e oferece métodos para registrá-los (
subscribe) e removê-los (unsubscribe). Quando seu estado muda, chamanotifypara percorrer a lista e avisar cada observador. -
Observer (interface): declara o método de atualização
que o Subject chama. Ex.:
update(data). -
ConcreteObserver: implementa
Observere 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 doObserverao 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)
<?php
// ── Interfaces ────────────────────────────────────────────────
// PHP possui SplSubject e SplObserver nativos; aqui usamos
// interfaces manuais para maior clareza e tipagem explícita.
interface DadosMeteo
{
public function getTemperatura(): float;
public function getUmidade(): float;
}
interface ObserverMeteo
{
public function update(DadosMeteo $data): void;
}
// ── Value Object ──────────────────────────────────────────────
final class Medicao implements DadosMeteo
{
public function __construct(
private readonly float $temperatura,
private readonly float $umidade
) {}
public function getTemperatura(): float { return $this->temperatura; }
public function getUmidade(): float { return $this->umidade; }
}
// ── ConcreteSubject ───────────────────────────────────────────
class EstacaoMeteorologica
{
/** @var ObserverMeteo[] */
private array $observers = [];
public function subscribe(ObserverMeteo $observer): void
{
$this->observers[] = $observer;
}
public function unsubscribe(ObserverMeteo $observer): void
{
$this->observers = array_filter(
$this->observers,
fn($o) => $o !== $observer
);
}
public function setMedicao(float $temperatura, float $umidade): void
{
$dados = new Medicao($temperatura, $umidade);
foreach ($this->observers as $observer) {
$observer->update($dados);
}
}
}
// ── ConcreteObservers ─────────────────────────────────────────
class PainelAtual implements ObserverMeteo
{
public function update(DadosMeteo $data): void
{
printf(
"[Painel] Temp: %.1fC Umidade: %.1f%%\n",
$data->getTemperatura(),
$data->getUmidade()
);
}
}
class DisplayAlertas implements ObserverMeteo
{
public function update(DadosMeteo $data): void
{
if ($data->getTemperatura() > 35) {
echo "[ALERTA] Temperatura critica: " . $data->getTemperatura() . "C\n";
}
if ($data->getUmidade() < 30) {
echo "[ALERTA] Umidade baixa: " . $data->getUmidade() . "%\n";
}
}
}
class GraficoHistorico implements ObserverMeteo
{
/** @var DadosMeteo[] */
private array $historico = [];
public function update(DadosMeteo $data): void
{
$this->historico[] = $data;
echo "[Grafico] Registros: " . count($this->historico) . "\n";
}
}
// ── Uso ──────────────────────────────────────────────────────
$estacao = new EstacaoMeteorologica();
$painel = new PainelAtual();
$alertas = new DisplayAlertas();
$grafico = new GraficoHistorico();
$estacao->subscribe($painel);
$estacao->subscribe($alertas);
$estacao->subscribe($grafico);
$estacao->setMedicao(25, 60);
// [Painel] Temp: 25.0C Umidade: 60.0%
// [Grafico] Registros: 1
$estacao->setMedicao(38, 25);
// [Painel] Temp: 38.0C Umidade: 25.0%
// [ALERTA] Temperatura critica: 38C
// [ALERTA] Umidade baixa: 25%
// [Grafico] Registros: 2
// Cancelar inscrição do painel.
$estacao->unsubscribe($painel);
$estacao->setMedicao(20, 70);
// [Grafico] Registros: 3
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)
<?php
// Modelo pull em PHP — Observer recebe referência ao Subject.
interface PedidoObserver
{
public function onPedidoAtualizado(Pedido $pedido): void;
}
class Pedido
{
/** @var PedidoObserver[] */
private array $observers = [];
private string $status = 'rascunho';
/** @var string[] */
private array $itens = [];
public function subscribe(PedidoObserver $o): void
{
$this->observers[] = $o;
}
public function unsubscribe(PedidoObserver $o): void
{
$this->observers = array_filter($this->observers, fn($obs) => $obs !== $o);
}
public function getStatus(): string { return $this->status; }
public function getItens(): array { return $this->itens; }
public function getTotal(): float { return count($this->itens) * 50.0; }
public function adicionarItem(string $item): void
{
$this->itens[] = $item;
$this->notificar();
}
public function avancarStatus(string $novoStatus): void
{
$this->status = $novoStatus;
$this->notificar();
}
private function notificar(): void
{
foreach ($this->observers as $o) {
$o->onPedidoAtualizado($this);
}
}
}
class LogDeStatus implements PedidoObserver
{
public function onPedidoAtualizado(Pedido $pedido): void
{
echo "[Log] Status atual: " . $pedido->getStatus() . "\n";
}
}
class CalculadoraTotais implements PedidoObserver
{
public function onPedidoAtualizado(Pedido $pedido): void
{
printf(
"[Total] R$%.2f (%d itens)\n",
$pedido->getTotal(),
count($pedido->getItens())
);
}
}
// ── Uso ──────────────────────────────────────────────────────
$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.