Event Bus y el patrón Observer en C# (.NET): de la teoría a la implementación
En el desarrollo moderno con C# (.NET), a menudo surge la necesidad de organizar la interacción entre componentes del sistema, manteniendo su independencia. El patrón Observer y su evolución — Event Bus (Bus de eventos) — son herramientas clave para implementar el acoplamiento débil (loose coupling). En este artículo, analizaremos cómo funcionan estos mecanismos y crearemos una implementación simple pero funcional de un Event Bus en C#.
Patrón Observer: el enfoque clásico
El patrón Observer define una relación de "uno a muchos" entre objetos: cuando un objeto (publicador) cambia su estado, todos los objetos dependientes (suscriptores) son notificados automáticamente. En .NET, este patrón está integrado a través de eventos (event) y delegados.
Desventajas de la implementación clásica:
- Dependencia directa del suscriptor respecto al publicador (es necesario almacenar una referencia al objeto).
- Complejidad en la gestión de suscripciones en sistemas grandes.
- Problemas con hilos (la concurrencia requiere sincronización adicional).
Event Bus: mediador centralizado
Event Bus resuelve estos problemas actuando como un mediador central. En lugar de suscribirse directamente a un objeto específico, los componentes se registran en el bus de eventos. Los publicadores publican eventos en el bus, y el bus los distribuye a todos los suscriptores. Esto implementa el patrón Pub/Sub (Publish/Subscribe).
Ventajas de Event Bus:
- Acoplamiento débil: los publicadores y suscriptores no saben nada el uno del otro.
- Escalabilidad: es fácil agregar nuevos suscriptores.
- Versatilidad: adecuado para GUI (WPF/WinForms), microservicios y juegos (Unity).
Implementación de Event Bus en C# (.NET)
Crearemos una implementación básica de Event Bus que soporte procesamiento asíncrono y eventos tipados. Para ello, utilizaremos la interfaz IEvent y la clase genérica EventBus.
Paso 1: Definir los tipos base.
// Interfaz base para todos los eventospublic interface IEvent { }
// Ejemplo de evento - mensaje de usuariopublic class UserCreatedEvent : IEvent{ public int UserId { get; } public string Email { get; }
public UserCreatedEvent(int userId, string email) { UserId = userId; Email = email; }}Paso 2: Crear el bus de eventos.
using System;using System.Collections.Concurrent;using System.Threading.Tasks;
public class EventBus{ // Almacenamiento de suscriptores: clave - tipo de evento, valor - lista de manejadores private readonly ConcurrentDictionary<Type, ConcurrentBag<object>> _subscribers = new();
// Suscripción a un evento public void Subscribe<TEvent>(Func<TEvent, Task> handler) where TEvent : IEvent { var eventType = typeof(TEvent); var handlers = _subscribers.GetOrAdd(eventType, _ => new ConcurrentBag<object>()); handlers.Add(handler); }
// Cancelar suscripción (simplificada - elimina todos los manejadores de este tipo) public void Unsubscribe<TEvent>() where TEvent : IEvent { _subscribers.TryRemove(typeof(TEvent), out _); }
// Publicación de evento (asíncrona) public async Task PublishAsync<TEvent>(TEvent eventData) where TEvent : IEvent { if (!_subscribers.TryGetValue(typeof(TEvent), out var handlers)) return;
foreach (var handler in handlers) { if (handler is Func<TEvent, Task> typedHandler) { await typedHandler(eventData); } } }}Paso 3: Uso en la aplicación.
public class