El patrón Observer define una dependencia uno-a-muchos entre objetos, de modo que cuando uno cambia de estado todos sus observadores son notificados y actualizados automáticamente. Se basa en una suscripción desacoplada que permite reaccionar a eventos sin que el emisor conozca detalles de los receptores.
Es la piedra angular de arquitecturas reactivas, buses de eventos y interfaces gráficas: separa la emisión de cambios de su consumo, fomenta la extensibilidad y habilita componer comportamientos observando las mismas fuentes de datos.
En sistemas interactivos es habitual que varios componentes necesiten reaccionar ante un mismo cambio: actualizar dashboards cuando llega un pedido, recalcular stock, enviar notificaciones y registrar auditorías. Implementarlo sin Observer conduce a acoplamientos fuertes donde el sujeto debe invocar explícitamente a cada interesado, lo que genera dependencias cíclicas y dificulta agregar nuevos suscriptores.
Observer introduce un mecanismo de publicación-suscripción in-process: el sujeto sólo conoce una interfaz genérica de observador y notifica a todos los suscritos cuando ocurre un evento, evitando condicionales y referencias directas.
La intención es definir un protocolo para que objetos interesados se suscriban a cambios en otro objeto sin acoplamiento fuerte. Su aplicación es pertinente cuando:
El patrón motiva a separar la publicación de eventos de la lógica de consumo y a mantener un contrato estable que permita crecer en funcionalidades sin tocar el sujeto.
Observer cuenta con los siguientes participantes:
La notificación puede ser sincrónica (el sujeto llama secuencialmente a cada observador) o asincrónica delegando en hilos, colas o reactores según las necesidades de escalabilidad.
Elegir cómo se distribuyen los eventos es crucial:
Las garantías de entrega (al menos una, exactamente una, como máximo una) dependen de la estrategia seleccionada y de la idempotencia de los observadores. Observer proporciona la base, pero la confiabilidad debe diseñarse caso a caso.
Consideremos un marketplace que desea notificar en tiempo real cuando un pedido cambia de estado. Distintas áreas necesitan reaccionar: operaciones logísticas actualiza rutas, marketing envía mensajes y compliance registra auditorías. Las suscripciones deben poder activarse o desactivarse sin interrumpir el sistema.
Observer permite que el centro de eventos de pedidos publique cada cambio y que las áreas interesadas se suscriban, agregando nuevos observadores según las innovaciones del negocio sin modificar el código del sujeto.
El siguiente código muestra una implementación con suscriptores independientes y protección frente a condiciones de carrera mediante una sección crítica controlada por lock
:
using System;
using System.Collections.Generic;
namespace Tutorial.Observer
{
public enum EstadoPedido
{
Creado,
Preparado,
Enviado,
Entregado,
Cancelado
}
public sealed class EventoCambioEstado
{
public EventoCambioEstado(string idPedido, EstadoPedido estado, DateTimeOffset fecha, string origen)
{
IdPedido = idPedido ?? throw new ArgumentNullException(nameof(idPedido));
Estado = estado;
Fecha = fecha;
Origen = origen ?? throw new ArgumentNullException(nameof(origen));
}
public string IdPedido { get; }
public EstadoPedido Estado { get; }
public DateTimeOffset Fecha { get; }
public string Origen { get; }
}
public interface IObservadorCambioEstado
{
void Notificar(EventoCambioEstado evento);
}
public interface ICentroEventosPedido
{
void Suscribir(IObservadorCambioEstado observador);
void Cancelar(IObservadorCambioEstado observador);
void Publicar(EventoCambioEstado evento);
}
public sealed class CentroEventosPedido : ICentroEventosPedido
{
private readonly List<IObservadorCambioEstado> _observadores = new List<IObservadorCambioEstado>();
private readonly object _candado = new object();
public void Suscribir(IObservadorCambioEstado observador)
{
if (observador == null) throw new ArgumentNullException(nameof(observador));
lock (_candado)
{
if (!_observadores.Contains(observador))
{
_observadores.Add(observador);
}
}
}
public void Cancelar(IObservadorCambioEstado observador)
{
if (observador == null) return;
lock (_candado)
{
_observadores.Remove(observador);
}
}
public void Publicar(EventoCambioEstado evento)
{
if (evento == null) throw new ArgumentNullException(nameof(evento));
List<IObservadorCambioEstado> copia;
lock (_candado)
{
copia = new List<IObservadorCambioEstado>(_observadores);
}
foreach (var observador in copia)
{
observador.Notificar(evento);
}
}
}
public sealed class ObservadorLogistica : IObservadorCambioEstado
{
public void Notificar(EventoCambioEstado evento)
{
Console.WriteLine($"[Logistica] Pedido {evento.IdPedido} => {evento.Estado} (origen: {evento.Origen})");
}
}
public sealed class ObservadorMarketing : IObservadorCambioEstado
{
public void Notificar(EventoCambioEstado evento)
{
Console.WriteLine($"[Marketing] Preparar campana para {evento.IdPedido} en estado {evento.Estado}");
}
}
public sealed class ObservadorAuditoria : IObservadorCambioEstado
{
public void Notificar(EventoCambioEstado evento)
{
Console.WriteLine($"[Auditoria] Registro de {evento.IdPedido} a las {evento.Fecha:O}");
}
}
public sealed class ServicioPedidos
{
private readonly ICentroEventosPedido _centro;
public ServicioPedidos(ICentroEventosPedido centro)
{
_centro = centro ?? throw new ArgumentNullException(nameof(centro));
}
public void CambiarEstado(string idPedido, EstadoPedido estado, string origen)
{
var evento = new EventoCambioEstado(idPedido, estado, DateTimeOffset.UtcNow, origen);
_centro.Publicar(evento);
}
}
public static class AplicacionObserver
{
public static void Main()
{
ICentroEventosPedido centroEventos = new CentroEventosPedido();
centroEventos.Suscribir(new ObservadorLogistica());
centroEventos.Suscribir(new ObservadorMarketing());
centroEventos.Suscribir(new ObservadorAuditoria());
var servicio = new ServicioPedidos(centroEventos);
servicio.CambiarEstado("PED-1001", EstadoPedido.Preparado, "backend");
servicio.CambiarEstado("PED-1001", EstadoPedido.Enviado, "sistema-rastreos");
servicio.CambiarEstado("PED-1001", EstadoPedido.Entregado, "app-repartidores");
}
}
}
ServicioPedidos
actúa como sujeto y delega la notificación en CentroEventosPedido
, que mantiene una lista segura para acceso concurrente. Cada observador aplica sus propias reglas sin conocer a los demás. Agregar un nuevo suscriptor o remover uno existente no requiere modificar el servicio.
El uso de un lock
centralizado asegura que la lista de observadores se mantenga consistente aun cuando múltiples hilos intenten suscribirse o cancelar al mismo tiempo. Si la cantidad de eventos creciera, podría reemplazarse por estructuras concurrentes o por colas asíncronas administradas por Task
.
La biblioteca base de .NET incluye IObservable<T>
e IObserver<T>
para modelar flujos de eventos desacoplados, junto con utilidades como PropertyChangedEventHandler
para notificar cambios de propiedades. Frameworks como WPF, WinUI, Blazor o ASP.NET Core se apoyan en estos contratos y, cuando se necesita mayor sofisticación, Reactive Extensions (Rx.NET) permite componer consultas sobre secuencias, aplicar retro-presión y orquestar tareas asíncronas.
Observer puede combinarse con patrones de mensajes cuando la escala crece. Las variantes incluyen:
En escenarios reactivamente complejos surgen soluciones como Reactive Streams, que formalizan este patrón incorporando presión inversa y cancelaciones.
Un riesgo común es olvidar cancelar suscripciones: los observadores pueden seguir recibiendo notificaciones innecesarias o provocar fugas de memoria. Otro problema es notificar sin protección ante errores; si un observador lanza una excepción sin control, el resto de suscriptores puede quedar sin aviso. También se debe evitar que los observadores modifiquen el sujeto de forma directa, para impedir actualizaciones recursivas.
Observer se complementa con Mediator cuando se necesita coordinar la comunicación entre observadores. Puede convivir con Command para encapsular las acciones disparadas por cada notificación y con Chain of Responsibility cuando los eventos atraviesan filtros secuenciales. Además, es un fundamento clave para la infraestructura de eventos utilizada por Strategy y State para reaccionar ante cambios externos.