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.