Event Bus e o padrão Observer em C# (.NET): da teoria à prática
No desenvolvimento moderno em C# (.NET), um dos princípios-chave é o baixo acoplamento (loose coupling) entre os componentes do sistema. O padrão Observer (Observador) e sua evolução — o Event Bus (Barramento de Eventos) — fornecem um mecanismo elegante para organizar a interação entre objetos sem dependências diretas. Neste artigo, vamos detalhar como implementar um Event Bus em C# (.NET), analisar sua arquitetura e fornecer exemplos práticos.
O que é o padrão Observer?
O padrão Observer define uma relação "um para muitos" entre objetos: quando um objeto (sujeito) altera seu estado, todos os objetos que dependem dele (observadores) são automaticamente notificados e atualizados. Em C#, esse padrão é tradicionalmente implementado por meio de eventos (event) e delegados.
O problema da implementação clássica
Embora os eventos embutidos do C# funcionem bem, eles têm limitações:
- Conexão direta: o publicador precisa conhecer explicitamente os assinantes.
- Dificuldade de cancelamento de assinatura: se um assinante esquecer de cancelar a assinatura, ocorre vazamento de memória.
- Flexibilidade limitada: é difícil organizar processamento assíncrono ou filtragem de eventos.
O que é um Event Bus?
Um Event Bus é um barramento centralizado (mediador) que implementa o padrão Publisher-Subscriber (Publicador-Assinante). Diferentemente do Observer clássico, o publicador e o assinante não se conhecem. Eles se comunicam apenas por meio do barramento:
- Publicador envia uma mensagem (evento) para o barramento.
- Barramento encontra todos os assinantes para esse tipo de evento e os notifica.
- Assinante se registra para um tipo específico de evento e o processa.
Vantagens do Event Bus
- Baixo acoplamento: os componentes não dependem uns dos outros.
- Escalabilidade: é fácil adicionar novos assinantes.
- Testabilidade: é possível substituir o barramento por mocks.
- Assincronia: é possível processar eventos em threads em segundo plano.
Implementação de um Event Bus em C# (.NET)
Vamos considerar uma implementação simples, mas funcional. Criaremos uma classe EventBus que armazenará um dicionário de assinantes.
public interface IEventBus{ void Publish<TEvent>(TEvent eventData) where TEvent : class; IDisposable Subscribe<TEvent>(Action<TEvent> handler) where TEvent : class;}
public class EventBus : IEventBus{ private readonly Dictionary<Type, List<Delegate>> _subscribers = new(); private readonly object _lock = new();
public void Publish<TEvent>(TEvent eventData) where TEvent : class { List<Delegate> handlers; lock (_lock) { if (!_subscribers.TryGetValue(typeof(TEvent), out var list)) return; handlers = new List<Delegate>(list); }
foreach (var handler in handlers) { if (handler is Action<TEvent> action) action(eventData); } }
public IDisposable Subscribe<TEvent>(Action<TEvent> handler) where TEvent : class { lock (_lock) { if (!_subscribers.ContainsKey(typeof(TEvent))) _subscribers[typeof(TEvent)] = new List<Delegate>(); _subscribers[typeof(TEvent)].Add(handler); }
return new Unsubscriber(() => { lock (_lock) { if (_subscribers.TryGetValue(typeof(TEvent), out var list)) list.Remove(handler); } Também na biblioteca
Geolocalização e Mapas em PHP: Visão Geral de Bibliotecas e Ferramentas
Geração de PDF em TypeScript: bibliotecas e ferramentas para Node.js
Geração de relatórios PDF em JavaScript (Node.js): Puppeteer, PDFKit, jsPDF
Biblioteca WebSocket Kotlin: Cliente e Servidor WebSocket Ktor