Event Bus e o padrão Observer em TypeScript
O padrão Observer (Observador) e sua implementação prática — Event Bus (Barramento de Eventos) — são ferramentas fundamentais para construir aplicações fracamente acopladas e escaláveis. Neste artigo, analisaremos detalhadamente como implementar um Event Bus em TypeScript, usando tipagem estrita, e veremos sua aplicação em projetos educacionais.
O que é o padrão Observer?
O padrão Observer define uma relação "um para muitos" entre objetos. Quando um objeto (publicador, subject) altera seu estado, todos os objetos dependentes dele (observadores, observers) são automaticamente notificados e atualizados. Isso evita o acoplamento rígido entre componentes.
O que é um Event Bus?
Event Bus (barramento de eventos) é um mecanismo centralizado que implementa o padrão Observer. Em vez de inscrever observadores diretamente no publicador, todos os componentes se comunicam através de um barramento único. Um componente pode:
- Inscrever-se (subscribe/listen) em um evento específico.
- Publicar (emit/publish) um evento, que será transmitido a todos os inscritos.
- Cancelar inscrição (unsubscribe) de um evento quando não for mais necessário.
Essa abordagem é ideal para:
- Interação entre módulos que não precisam conhecer uns aos outros.
- Tratamento de ações do usuário (cliques, entrada de dados).
- Reação a mudanças de estado da aplicação (carregamento de dados, erros).
Implementação de um Event Bus em TypeScript
TypeScript adiciona um poderoso sistema de tipos, tornando o Event Bus seguro e previsível. Criaremos um barramento tipado, onde cada evento tem um conjunto estritamente definido de argumentos.
1. Definição dos tipos de eventos
Primeiro, definimos uma interface para mapear nomes de eventos e seus argumentos:
// Definimos um mapa de eventos: chave — nome do evento, valor — tipo dos argumentosinterface EventMap { 'user:login': { userId: string; timestamp: number }; 'data:loaded': { items: string[] }; 'error:occurred': { message: string; code: number }; // Podemos adicionar quaisquer outros eventos}2. Criação da classe EventBus
Agora criamos a classe em si, usando genéricos para tipagem estrita:
type Listener<T> = (payload: T) => void;
class EventBus<T extends Record<string, unknown>> { private listeners: Map<keyof T, Set<Listener<unknown>>> = new Map();
// Inscrição em um evento subscribe<K extends keyof T>(event: K, listener: Listener<T[K]>): () => void { if (!this.listeners.has(event)) { this.listeners.set(event, new Set()); } const currentListeners = this.listeners.get(event)!; currentListeners.add(listener as Listener<unknown>);
// Retorna uma função para cancelar a inscrição return () => { currentListeners.delete(listener as Listener<unknown>); }; }
// Publicação de um evento emit<K extends keyof T>(event: K, payload: T[K]): void { const currentListeners = this.listeners.get(event); if (currentListeners) { currentListeners.forEach((listener) => { listener(payload); }); } }
// Cancelar inscrição de todos os eventos (útil para limpeza) clear(): void { this.listeners.clear(); }}
// Criamos uma instância do barramento com nosso mapa de eventosexport const appEventBus = new EventBus<EventMap>();3. Exemplo de uso
Vejamos como diferentes módulos podem interagir através do Event Bus:
// Módulo de autenticaçãoclass AuthService { login(userId: string) { // ... lógica de login ... appEventBus.emit('use