Design Patterns: Observer (Parte 7)

·4 min de leitura

Contexto

Depois do Facade (Parte 6), voltamos a um clássico comportamental: Observer. A intenção do GoF é definir uma dependência "um-para-muitos" entre objetos, de modo que quando um muda de estado, todos os interessados sejam notificados, sem acoplar o "sujeito" a uma lista fixa de classes e sem criar um if para cada reação.

O conceito de "um-para-muitos" é muito importante entender. É a base do Observer. Um classe pode ser observada por muitas outras classes. Quando ela muda de estado, todas as classes observadores são notificadas.

Na prática, Observer aparece sempre que uma mudança em um lugar precisa disparar efeitos em outros. E aqui existe um detalhe importante: em aplicações modernas, você frequentemente resolve isso com um event dispatcher do framework (Symfony, Laravel etc.). Mesmo assim, entender Observer ajuda a tomar decisões melhores, principalmente para separar o que é regra de domínio do que é mecanismo de infraestrutura.

Exemplo

Imagine um sistema de e-commerce. Quando um pedido é confirmado, várias coisas precisam acontecer: reservar estoque, enviar e-mail de confirmação, registrar auditoria. Sem Observer, tudo isso vira uma sequência de ifs dentro do método confirm(). Com Observer, cada reação vive isolada e pode ser adicionada ou removida sem tocar no pedido.

Primeiro, o evento. É um objeto imutável que carrega apenas os dados relevantes do que aconteceu:

<?php

final readonly class OrderConfirmed
{
    public function __construct(
        public string $orderId,
        public string $customerEmail,
        public float $total,
    ) {}
}

Quem quiser reagir a esse evento precisa seguir um contrato. A interface define o método handle que todo listener deve implementar:

interface OnOrderConfirmed
{
    public function handle(OrderConfirmed $event): void;
}

Agora o serviço que coordena tudo. OrderService mantém uma lista de listeners e, ao confirmar um pedido, cria o evento e notifica todos os inscritos. Ele não sabe quem são, só sabe que implementam OnOrderConfirmed:

final class OrderService
{
    /** @var list<OnOrderConfirmed> */
    private array $listeners = [];

    public function subscribe(OnOrderConfirmed $listener): void
    {
        $this->listeners[] = $listener;
    }

    public function confirm(string $orderId, string $customerEmail, float $total): void
    {
        // persiste pedido, muda status, etc.

        $event = new OrderConfirmed($orderId, $customerEmail, $total);

        foreach ($this->listeners as $listener) {
            $listener->handle($event);
        }
    }
}

Cada reação vive na sua própria classe. Adicionar ou remover um comportamento não exige mexer em OrderService:

final readonly class ReserveInventory implements OnOrderConfirmed
{
    public function handle(OrderConfirmed $event): void
    {
        echo "Estoque reservado para pedido {$event->orderId}\n";
    }
}

final readonly class SendConfirmationEmail implements OnOrderConfirmed
{
    public function handle(OrderConfirmed $event): void
    {
        echo "E-mail enviado para {$event->customerEmail} — total R$ {$event->total}\n";
    }
}

final readonly class AuditLog implements OnOrderConfirmed
{
    public function handle(OrderConfirmed $event): void
    {
        echo "[AUDIT] Pedido {$event->orderId} confirmado\n";
    }
}

Por fim, o uso. Registramos os listeners e confirmamos o pedido:

$service = new OrderService();
$service->subscribe(new ReserveInventory());
$service->subscribe(new SendConfirmationEmail());
$service->subscribe(new AuditLog());

$service->confirm('ORD-42', 'cliente@email.com', 189.90);

// Saída:
// Estoque reservado para pedido ORD-42
// E-mail enviado para cliente@email.com — total R$ 189.9
// [AUDIT] Pedido ORD-42 confirmado

Adicionar uma nova reação (enviar para o financeiro, disparar webhook, etc.) é criar uma classe que implementa OnOrderConfirmed e registrar, sem tocar em confirm(). Em um domínio mais "puro", você pode preferir publicar o evento por um serviço de aplicação e deixar a infraestrutura cuidar da entrega, mas a lógica central é a mesma.

Armadilhas

Observer pode virar difícil de rastrear quando usado sem critério. A ordem dos observers às vezes passa a importar (e isso precisa ser explicitado), notificações síncronas podem virar gargalo e, se "tudo vira evento", o fluxo do sistema fica invisível. Em sistemas maiores, filas e mecanismos de entrega assíncrona são comuns, mas aí você está juntando padrão com arquitetura, e vale manter rastreabilidade (logs, correlação, métricas).

Conclusão

Observer é a base mental de eventos e pub/sub locais. Saiba reconhecer quando você só precisa de um dispatcher do framework e quando vale modelar explicitamente no domínio.

Referências

  • Gamma, E. et al. Design Patterns (GoF).
  • Refactoring Guru – Observer: https://refactoring.guru/design-patterns/observer

Links