Design Patterns

Observer

Quando um objeto muda, todos os interessados são notificados automaticamente

Conceito e intenção

Observer define uma dependência um-para-muitos entre objetos: quando o Subject (também chamado de Publisher ou Observable) muda de estado, todos os Observers (Subscribers, Listeners) registrados são notificados automaticamente.

O problema motivador é o acoplamento entre produtor e consumidores. Sem Observer, o OrderService que precisa notificar EmailService, AnalyticsService e LoyaltyService após um pedido teria que chamar cada um diretamente — acoplamento explícito, violando o Princípio de Inversão de Dependência. Adicionar um novo consumidor exige modificar o produtor.

Observer inverte essa dependência: o produtor conhece apenas a interface genérica Observer. Cada consumidor se registra no produtor. O produtor não sabe quem são os ouvintes — ele apenas notifica a lista. Adicionar um novo consumidor = novo Observer que se registra, sem tocar no produtor.


Estrutura

┌────────────────────────────┐
│  Subject (Observable)      │
│  - observers: List         │
│  + subscribe(Observer)     │
│  + unsubscribe(Observer)   │
│  + notify()                │
└────────────────────────────┘
           │ notifica

┌─────────────────────┐
│  <<interface>>      │
│     Observer        │
│  + update(event)    │
└─────────────────────┘

    ┌──────┴──────┐
┌───┴────┐   ┌───┴────┐
│ObsrA   │   │ObsrB   │
└────────┘   └────────┘

Participantes:

  • Subject — mantém lista de observers e notifica quando muda
  • Observer — interface com o método de callback
  • ConcreteObserver — reage ao evento de forma específica

Implementação Java manual

// Evento de domínio como record imutável
public record OrderPlacedEvent(
    String orderId,
    String customerId,
    Money total,
    List<OrderItem> items,
    Instant occurredAt
) {}

// Interface do Observer
public interface OrderEventListener {
    void onOrderPlaced(OrderPlacedEvent event);
}

// Subject — mantém e notifica observers
@Service
public class OrderService {
    private final OrderRepository repository;
    private final List<OrderEventListener> listeners = new CopyOnWriteArrayList<>();

    public OrderService(OrderRepository repository) {
        this.repository = repository;
    }

    public void subscribe(OrderEventListener listener) {
        listeners.add(listener);
    }

    public void unsubscribe(OrderEventListener listener) {
        listeners.remove(listener);
    }

    public Order placeOrder(Cart cart, Customer customer) {
        Order order = Order.from(cart, customer);
        repository.save(order);

        // Notifica todos os observers — o OrderService não conhece nenhum deles
        OrderPlacedEvent event = new OrderPlacedEvent(
            order.getId(), customer.getId(), order.getTotal(), order.getItems(), Instant.now()
        );
        listeners.forEach(listener -> listener.onOrderPlaced(event));

        return order;
    }
}

// Observers concretos — cada um reage de forma independente
@Component
public class EmailNotifier implements OrderEventListener {
    private final EmailService emailService;

    public EmailNotifier(EmailService emailService) {
        this.emailService = emailService;
    }

    @Override
    public void onOrderPlaced(OrderPlacedEvent event) {
        emailService.sendOrderConfirmation(event.customerId(), event.orderId());
    }
}

@Component
public class AnalyticsTracker implements OrderEventListener {
    private final AnalyticsService analytics;

    public AnalyticsTracker(AnalyticsService analytics) {
        this.analytics = analytics;
    }

    @Override
    public void onOrderPlaced(OrderPlacedEvent event) {
        analytics.track("order_placed", Map.of(
            "orderId", event.orderId(),
            "revenue", event.total().toDouble()
        ));
    }
}

@Component
public class LoyaltyPointsAccruer implements OrderEventListener {
    private final LoyaltyService loyalty;

    public LoyaltyPointsAccruer(LoyaltyService loyalty) {
        this.loyalty = loyalty;
    }

    @Override
    public void onOrderPlaced(OrderPlacedEvent event) {
        loyalty.creditPoints(event.customerId(), event.total());
    }
}

// Registro dos observers (ex: em @PostConstruct ou @Configuration)
@Configuration
public class OrderEventConfig {
    @Bean
    public CommandLineRunner registerListeners(
            OrderService orderService,
            EmailNotifier email,
            AnalyticsTracker analytics,
            LoyaltyPointsAccruer loyalty) {
        return args -> {
            orderService.subscribe(email);
            orderService.subscribe(analytics);
            orderService.subscribe(loyalty);
        };
    }
}

Com Spring Events (forma idiomática em Spring Boot)

Spring fornece um mecanismo de eventos embutido baseado em Observer, com descoberta automática de listeners:

// Evento
public record OrderPlacedEvent(String orderId, String customerId, Money total) {}

// Publisher — não conhece nenhum listener
@Service
public class OrderService {
    private final OrderRepository repository;
    private final ApplicationEventPublisher publisher;

    public OrderService(OrderRepository repository, ApplicationEventPublisher publisher) {
        this.repository = repository;
        this.publisher  = publisher;
    }

    @Transactional
    public Order placeOrder(Cart cart, Customer customer) {
        Order order = Order.from(cart, customer);
        repository.save(order);
        // Spring descobre e notifica todos os @EventListener automaticamente
        publisher.publishEvent(new OrderPlacedEvent(order.getId(), customer.getId(), order.getTotal()));
        return order;
    }
}

// Listeners — descobertos automaticamente pelo contexto Spring
@Component
public class OrderEventHandlers {

    private final EmailService emailService;
    private final AnalyticsService analytics;
    private final LoyaltyService loyalty;
    private final InventoryService inventory;

    // Construtor com todas as dependências...

    @EventListener
    public void onOrderPlaced(OrderPlacedEvent event) {
        emailService.sendConfirmation(event.customerId(), event.orderId());
    }

    // Processamento assíncrono — não bloqueia o fluxo principal
    @Async
    @EventListener
    public void trackAnalytics(OrderPlacedEvent event) {
        analytics.track("order_placed", event.orderId(), event.total().toDouble());
    }

    // Executado após o commit da transação (evita inconsistência)
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void creditLoyaltyPoints(OrderPlacedEvent event) {
        loyalty.creditPoints(event.customerId(), event.total());
    }
}

@TransactionalEventListener é especialmente útil: garante que o evento só seja processado se a transação de escrita for confirmada — evita notificar clientes de pedidos que serão revertidos.


Pull vs Push Model

Push model (padrão): o Subject envia os dados relevantes no evento. O Observer recebe tudo que precisa no callback.

// Push — evento contém os dados
void onOrderPlaced(OrderPlacedEvent event) {
    // event já tem orderId, customerId, total, items
    emailService.send(event.customerId(), buildEmail(event));
}

Pull model: o Subject notifica que algo mudou; o Observer consulta o Subject para obter o estado atual.

// Pull — Observer busca o estado necessário
void onOrderPlaced(String orderId) {
    Order order = orderRepository.findById(orderId).orElseThrow();
    emailService.send(order.getCustomerId(), buildEmail(order));
}

Push é mais eficiente (sem consulta extra) e mais desacoplado (o Observer não precisa de referência ao Subject). Pull é útil quando o Observer precisa de apenas parte do estado e o evento completo seria pesado.


Implementação TypeScript

// Event types
interface OrderPlacedEvent {
  orderId: string;
  customerId: string;
  total: number;
  items: OrderItem[];
  occurredAt: Date;
}

// Observer interface
type OrderEventListener = (event: OrderPlacedEvent) => void | Promise<void>;

// Subject
class OrderService {
  private readonly listeners = new Set<OrderEventListener>();

  subscribe(listener: OrderEventListener): () => void {
    this.listeners.add(listener);
    return () => this.listeners.delete(listener); // retorna unsubscribe
  }

  async placeOrder(cart: Cart, customerId: string): Promise<Order> {
    const order = Order.create(cart, customerId);
    await this.repository.save(order);

    const event: OrderPlacedEvent = {
      orderId: order.id,
      customerId,
      total: order.total,
      items: order.items,
      occurredAt: new Date(),
    };

    // Notifica todos os observers
    await Promise.all([...this.listeners].map(l => l(event)));
    return order;
  }
}

// EventEmitter nativo do Node.js (implementação built-in de Observer)
import { EventEmitter } from 'events';

class OrderEventBus extends EventEmitter {}
const bus = new OrderEventBus();

// Publicar
bus.emit('order:placed', { orderId: '123', customerId: 'c-01', total: 299.9 });

// Assinar
bus.on('order:placed', (event) => {
  console.log(`Pedido ${event.orderId} confirmado`);
});

// TypeScript tipado com EventEmitter customizado
type Events = {
  'order:placed': OrderPlacedEvent;
  'order:cancelled': { orderId: string; reason: string };
};

class TypedEventBus<T extends Record<string, unknown>> {
  private emitter = new EventEmitter();

  on<K extends keyof T>(event: K, listener: (data: T[K]) => void): void {
    this.emitter.on(event as string, listener);
  }

  emit<K extends keyof T>(event: K, data: T[K]): void {
    this.emitter.emit(event as string, data);
  }
}

RxJS Observable — Observer reativo

import { Subject, Observable } from 'rxjs';
import { filter, map, debounceTime } from 'rxjs/operators';

// Subject RxJS é simultaneamente Observable (publisher) e Observer
const orderEvents$ = new Subject<OrderPlacedEvent>();

// Subscriptions com operadores reativos
orderEvents$.pipe(
  filter(e => e.total > 1000),       // somente pedidos acima de R$1000
  debounceTime(500),                  // aguarda 500ms para agrupar eventos
  map(e => ({ ...e, isHighValue: true }))
).subscribe(event => {
  vipNotificationService.notify(event.customerId);
});

// Emitir
orderEvents$.next({ orderId: '123', customerId: 'c-01', total: 1500, ... });

No mundo real

java.util.Observable e Observer — classes do Java SDK (depreciadas no Java 9, mas importantes historicamente). Substituídas por java.beans.PropertyChangeSupport.

Java PropertyChangeListener — Observer pattern no Java Beans para mudanças de propriedades. Swing e JavaFX usam extensivamente.

Spring ApplicationEventPublisher / @EventListener — Observer event-driven integrado ao ciclo de vida do container Spring.

java.util.EventListener — interface marker que Listeners do Java Swing implementam (ActionListener, MouseListener, KeyListener).

DOM Events (addEventListener) — Observer built-in do browser. element.addEventListener('click', handler) registra um Observer para o evento de clique.

Node.js EventEmitter — base de toda a arquitetura assíncrona do Node. fs.createReadStream(), http.Server, process — todos são EventEmitters.

RxJS / Reactor / Project Reactor — frameworks reativos que estendem Observer com operadores funcionais (filter, map, flatMap, combineLatest) para composição de fluxos de dados assíncronos.


Quando usar

  • Múltiplos subsistemas precisam reagir ao mesmo evento sem que o produtor os conheça
  • Implementar domain events em arquitetura orientada a eventos
  • Desacoplar o produtor de mudanças dos consumidores (Dependency Inversion)
  • Notificações que podem ter zero, um ou múltiplos ouvintes
  • Quando o número de ouvintes varia em tempo de execução (plug-in systems)

Quando NÃO usar

  • Quando a ordem de notificação é crítica e precisa ser garantida — Observer não garante ordem sem controle explícito
  • Quando ouvintes causam efeitos colaterais em cadeia imprevisíveis (ouvidor A modifica estado que afeta ouvidor B) — use Mediator para coordenação explícita
  • Quando há apenas um consumidor fixo — chamada direta é mais simples e rastreável
  • Em fluxos críticos onde falha de um Observer não pode ser silenciada — observe o tratamento de erros

Combinações com outros padrões

Observer + Command: Cada notificação pode carregar um Command que o Observer executa, em vez de lógica embutida no callback. Permite log, undo e fila de eventos.

Observer + Mediator: Quando muitos objetos se observam mutuamente, criando dependências cruzadas, o Mediator centraliza a coordenação. Observer é para notificação um-para-muitos; Mediator é para comunicação muitos-para-muitos.

Observer + Decorator: Decorators (ex: @TransactionalEventListener) adicionam comportamento transversal ao mecanismo de notificação sem alterar o Subject ou os Observers.

Observer + Facade: A Facade pode usar internamente Observer para notificar sobre operações completadas, enquanto expõe uma interface síncrona simples ao cliente.