Event Bus y el patrón Observer en C# (.NET): de la teoría a la práctica
En el desarrollo moderno con C# (.NET), uno de los principios clave es el acoplamiento débil (loose coupling) entre los componentes del sistema. El patrón Observer (Observador) y su evolución — Event Bus (Bus de eventos) — proporcionan un mecanismo elegante para organizar la interacción entre objetos sin dependencias directas. En este artículo, analizaremos en detalle cómo implementar un Event Bus en C# (.NET), examinaremos su arquitectura y presentaremos ejemplos prácticos.
¿Qué es el patrón Observer?
El patrón Observer define una relación de «uno a muchos» entre objetos: cuando un objeto (sujeto) cambia su estado, todos los objetos que dependen de él (observadores) son notificados y actualizados automáticamente. En C#, este patrón se implementa tradicionalmente mediante eventos (event) y delegados.
El problema de la implementación clásica
Aunque los eventos integrados de C# funcionan bien, tienen limitaciones:
- Conexión directa: el publicador debe conocer explícitamente a los suscriptores.
- Dificultad para cancelar la suscripción: si un suscriptor olvida cancelar la suscripción, se produce una fuga de memoria.
- Flexibilidad limitada: es difícil organizar el procesamiento asíncrono o el filtrado de eventos.
¿Qué es un Event Bus?
Un Event Bus es un bus centralizado (mediador) que implementa el patrón Publisher-Subscriber (Publicador-Suscriptor). A diferencia del Observer clásico, el publicador y el suscriptor no se conocen entre sí. Se comunican únicamente a través del bus:
- Publicador: envía un mensaje (evento) al bus.
- Bus: encuentra a todos los suscriptores de ese tipo de evento y los notifica.
- Suscriptor: se registra para un tipo de evento específico y lo procesa.
Ventajas de Event Bus
- Acoplamiento débil: los componentes no dependen unos de otros.
- Escalabilidad: es fácil agregar nuevos suscriptores.
- Capacidad de prueba: se puede sustituir el bus con mocks.
- Asincronía: se pueden procesar eventos en hilos en segundo plano.
Implementación de Event Bus en C# (.NET)
Consideremos una implementación simple pero funcional. Crearemos una clase EventBus que almacenará un diccionario de suscriptores.
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); } También en la biblioteca
Limitación de tasa en Rust: bibliotecas governor, leaky-bucket y middleware para servidores web
Internacionalización (i18n) en Kotlin: Guía completa con ejemplos
Criptografía en C# (.NET): cifrado y hash de datos