Pular para o conteúdo

Observer

Definir uma dependência um-para-muitos entre objetos, de modo que, quando um objeto muda de estado, todos os seus dependentes sejam notificados e atualizados automaticamente.

Vários objetos precisam reagir a mudanças no estado de outro, mas acoplar o objeto observado diretamente a cada interessado o amarra a eles e viola o OCP a cada novo dependente. Precisamos que o subject notifique um número variável e desconhecido de observers, sem conhecê-los concretamente.

  • Subject (Observable): mantém a lista de observers e oferece subscribe/unsubscribe; ao mudar, chama notify.
  • Observer: interface com update(...), implementada por cada interessado.
  • ConcreteObserver: reage à notificação, eventualmente consultando o subject.

Há duas variantes de entrega: push (o subject envia os dados no update) e pull (o observer consulta o subject após ser avisado).

Prós

  • Baixo acoplamento: subject conhece apenas a interface Observer.
  • Broadcast dinâmico; observers entram e saem em runtime.
  • Novos observers sem alterar o subject (OCP).

Contras

  • Cascatas de notificação difíceis de prever e depurar.
  • Ordem de notificação geralmente indeterminada.
  • Vazamento de memória (lapsed listener) se observers não se desinscrevem.
  • Observer síncrono acopla o subject ao tempo de execução dos observers.

Quando há um único dependente conhecido e fixo (uma chamada direta é mais clara), ou quando a “reação” precisa de garantias transacionais/ordem estrita que o broadcast simples não oferece.

public class Order {
private final EmailService email = new EmailService();
private final InventoryService inventory = new InventoryService();
public void confirm() {
this.status = "CONFIRMED";
email.sendConfirmation(this);
inventory.reserveItems(this);
}
}

Cada novo efeito colateral (fidelidade, analytics, nota fiscal) exige editar Order.

public interface OrderObserver {
void onOrderConfirmed(Order order);
}
public class Order {
private final List<OrderObserver> observers = new ArrayList<>();
private String status;
public void subscribe(OrderObserver observer) {
observers.add(observer);
}
public void unsubscribe(OrderObserver observer) {
observers.remove(observer);
}
public void confirm() {
this.status = "CONFIRMED";
notifyObservers();
}
private void notifyObservers() {
for (OrderObserver o : List.copyOf(observers)) {
o.onOrderConfirmed(this);
}
}
public String status() { return status; }
}

Observers concretos:

public class EmailNotifier implements OrderObserver {
public void onOrderConfirmed(Order order) {
System.out.println("Enviando e-mail de confirmacao");
}
}
public class InventoryReserver implements OrderObserver {
public void onOrderConfirmed(Order order) {
System.out.println("Reservando itens em estoque");
}
}

Uso:

Order order = new Order();
order.subscribe(new EmailNotifier());
order.subscribe(new InventoryReserver());
order.confirm();

Order não conhece mais os serviços concretos; adicionar reações é assinar novos observers.

sequenceDiagram
    participant Client
    participant Order as Order (Subject)
    participant Email as EmailNotifier
    participant Inv as InventoryReserver

    Client->>Order: subscribe(email)
    Client->>Order: subscribe(inventory)
    Client->>Order: confirm()
    Order->>Order: status = CONFIRMED
    Order->>Email: onOrderConfirmed(order)
    Order->>Inv: onOrderConfirmed(order)

O Observer é a semente conceitual dos Domain Events e dos listeners. Em vez de um subject notificar observers em memória, o domínio publica um evento (OrderConfirmed) e handlers registrados reagem. Vale distinguir dois regimes:

  • Observer síncrono (in-process): a notificação roda na mesma thread e transação do subject. Simples e imediato, mas o subject fica acoplado ao tempo e às falhas dos observers — uma exceção num observer pode abortar a operação principal.
  • Eventos assíncronos (mensageria/event bus): o evento é publicado e processado fora do fluxo original — outra thread, outro processo, outro serviço. Ganha-se desacoplamento temporal, resiliência e escala, ao custo de consistência eventual, necessidade de idempotência e infraestrutura (broker, outbox).

A estrutura mental — algo aconteceu, interessados reagem — é a mesma do Observer; muda o mecanismo de entrega e as garantias. No Domain-Driven Design, Agregados emitem Domain Events que outros Agregados ou Use Cases consomem, tipicamente de forma assíncrona para preservar a autonomia de cada agregado.

  • Lapsed listener / vazamento de memória: observers que nunca se desinscrevem mantêm o subject vivo e acumulam handlers. Gerencie o ciclo de vida (unsubscribe, referências fracas).
  • Cascata de notificações e reentrância: um observer que altera o subject e dispara novas notificações, gerando laços difíceis de rastrear. Iterar sobre uma cópia da lista evita ConcurrentModificationException.
  • Observer síncrono com trabalho pesado ou I/O, travando o fluxo principal — prefira eventos assíncronos nesses casos.
  • Depender da ordem de notificação, que geralmente é indefinida.
  • Tratar Observer como Mediator: se você precisa de lógica de coordenação entre partes, veja Mediator.

1. Que tipo de dependência o Observer define e como o acoplamento é mantido baixo?

Resposta

Uma dependência um-para-muitos: quando o subject muda, todos os observers são notificados. O acoplamento fica baixo porque o subject conhece apenas a interface Observer, não os observers concretos — pode-se adicionar ou remover interessados sem alterá-lo.

2. Diferencie a variante push da pull.

Resposta

No push, o subject envia os dados relevantes já no update/notify. No pull, o subject apenas avisa que algo mudou e cada observer consulta o subject para obter o que precisa. Push é conveniente; pull dá mais autonomia ao observer e evita enviar dados desnecessários.

3. Como o Observer se relaciona com Domain Events e qual a diferença entre observer síncrono e evento assíncrono?

Resposta

Domain Events são a evolução do Observer: em vez de notificar observers em memória, o domínio publica um evento e handlers reagem. O observer síncrono roda na mesma thread/transação (imediato, mas acopla tempo e falhas). O evento assíncrono é processado fora do fluxo (desacoplamento temporal, resiliência, escala), ao custo de consistência eventual e idempotência.

4. Cite dois erros comuns e como evitá-los.

Resposta

(1) Lapsed listener/vazamento de memória: observers que não se desinscrevem — resolva gerenciando o ciclo de vida ou usando referências fracas. (2) ConcurrentModificationException e cascatas: um observer altera a lista ou o subject durante a notificação — itere sobre uma cópia da lista e evite reentrância descontrolada.