El Observer es un patrón que notifica a varios suscriptores cuando cambia un objeto. El emisor conoce el contrato de observación, pero no necesita conocer las clases concretas que reaccionan.
Vivimos en un mundo de notificaciones. Cuando un Youtuber sube un vídeo, te llega un aviso al móvil. Cuando alguien te etiqueta en Twitter, te salta una alerta.
¿Cómo funciona esto técnicamente?
Imaginad que queréis saber cuándo sale a la venta el nuevo iPhone. Tenéis dos opciones:
- Polling: preguntar a la tienda cada cinco minutos. Genera trabajo aunque no haya novedades.
- Push: pedir que te avisen cuando el producto llegue.
El patrón Observer implementa la segunda opción. Define una dependencia “uno a muchos”, de modo que cuando un objeto cambia su estado, notifica automáticamente a todos los que estén “escuchando”.
El Problema: Mantener la coherencia
Imagina que tienes una hoja de cálculo tipo Excel. Tenéis datos en la columna A y un gráfico de barras que representa esos datos.
Si el usuario cambia un valor en la celda A1, el gráfico debe actualizarse inmediatamente. Si tuviéramos el código acoplado, la celda tendría que saber que existe un gráfico:
// ❌ Acoplamiento fuerte
public void CambiarValor(int nuevoValor)
{
_valor = nuevoValor;
_grafico.Actualizar(); // ¡La celda conoce al gráfico!
_tablaDinamica.Recalcular(); // ¡Y a la tabla!
}Esto es un desastre. Si mañana añadís un tercer componente (ej: un validador de datos), tienes que tocar el código de la celda.
La Solución: Publicar / Suscribir
El patrón Observer rompe este acoplamiento separando dos roles:
- Subject (Sujeto / Observable): El que tiene la información (la Celda, el Youtuber). Mantiene una lista de suscriptores.
- Observer (Observador): El que quiere ser notificado (el Gráfico, el Suscriptor).
Cuando el Sujeto cambia, recorre su lista y avisa a todos: “¡Eh, he cambiado!”. Al Sujeto le da igual quién le escucha; solo sabe que tiene una lista de interesados.
Implementación Clásica en C# (Con Interfaces)
Vamos a simular un canal de noticias.
Las Interfaces
// La interfaz para el que recibe la noticia
public interface IObserver
{
void Update(string noticia);
}
// La interfaz para el que emite la noticia
public interface ISubject
{
void Attach(IObserver observer); // Suscribirse
void Detach(IObserver observer); // Desuscribirse
void Notify(); // Avisar a todos
}El Sujeto Concreto (Agencia de Noticias)
public class AgenciaNoticias : ISubject
{
// Lista de suscriptores
private List<IObserver> _suscriptores = new List<IObserver>();
private string _ultimaNoticia;
public void Attach(IObserver observer)
{
_suscriptores.Add(observer);
Console.WriteLine("Agencia: Nuevo suscriptor añadido.");
}
public void Detach(IObserver observer)
{
_suscriptores.Remove(observer);
Console.WriteLine("Agencia: Suscriptor eliminado.");
}
// Método que avisa a todos
public void Notify()
{
Console.WriteLine("Agencia: Notificando a todos los suscriptores...");
foreach (var observer in _suscriptores)
{
observer.Update(_ultimaNoticia);
}
}
// Lógica de negocio que dispara el evento
public void PublicarNuevaNoticia(string noticia)
{
Console.WriteLine($"\n📰 BOLETÍN: {noticia}");
_ultimaNoticia = noticia;
Notify();
}
}Los Observadores Concretos
Podemos tener suscriptores muy diferentes (TV, Radio, Periódico) reaccionando al mismo evento de formas distintas.
public class CanalTV : IObserver
{
private string _nombre;
public CanalTV(string nombre) => _nombre = nombre;
public void Update(string noticia)
{
Console.WriteLine($"📺 {_nombre} Interrumpe su emisión: {noticia}");
}
}
public class Radio : IObserver
{
public void Update(string noticia)
{
Console.WriteLine($"📻 Radio: Hablamos de la noticia '{noticia}'");
}
}Puesta en marcha
class Program
{
static void Main(string[] args)
{
var agencia = new AgenciaNoticias();
var tv1 = new CanalTV("Antena 3");
var radio = new Radio();
// 1. Suscripción
agencia.Attach(tv1);
agencia.Attach(radio);
// 2. Evento
agencia.PublicarNuevaNoticia("El gato se subió al árbol");
// 3. Uno se da de baja
agencia.Detach(radio);
// 4. Nuevo evento
agencia.PublicarNuevaNoticia("El gato bajó del árbol");
}
}Salida:
Agencia: Nuevo suscriptor añadido.
Agencia: Nuevo suscriptor añadido.
📰 BOLETÍN: El gato se subió al árbol
Agencia: Notificando a todos los suscriptores...
📺 Antena 3 Interrumpe su emisión: El gato se subió al árbol
📻 Radio: Hablamos de la noticia 'El gato se subió al árbol'
Agencia: Suscriptor eliminado.
📰 BOLETÍN: El gato bajó del árbol
Agencia: Notificando a todos los suscriptores...
📺 Antena 3 Interrumpe su emisión: El gato bajó del árbolImplementación “Nativa” en C# (Eventos)
En C# rara vez implementamos el patrón Observer “a mano” como arriba. El lenguaje tiene soporte nativo para ello mediante delegados y la palabra clave event. Es azúcar sintáctico para el patrón Observer.
// SUJETO
public class Boton
{
// Definimos el evento (Observer nativo)
public event EventHandler<string> Click;
public void Presionar()
{
Console.WriteLine("\n🖱️ Botón presionado.");
// Invocamos a los suscriptores si hay alguno
Click?.Invoke(this, "El usuario hizo click");
}
}
// CLIENTE
class Program
{
static void Main(string[] args)
{
var boton = new Boton();
// Suscripción usando += (Attach)
boton.Click += (sender, mensaje) => Console.WriteLine($"Acción 1: Guardar en BBDD ({mensaje})");
boton.Click += (sender, mensaje) => Console.WriteLine($"Acción 2: Reproducir sonido");
boton.Presionar();
}
}Observer y Reactive Extensions (Rx)
Si llevamos este patrón al extremo, entramos en el mundo de la Programación Reactiva.
En .NET, existen las interfaces IObservable<T> y IObserver<T>. Librerías como System.Reactive (Rx.NET) permiten tratar los eventos como flujos de datos (streams).
En lugar de simplemente reaccionar a un evento, puedes filtrar, transformar y combinar eventos: “Avísame si el usuario hace click, pero solo si no ha hecho click en el último segundo (Debounce) y si el botón es el izquierdo”.
Esto es esencialmente el patrón Observer con superpoderes, combinando Observer con Iterator.
¿Cuándo usar Observer?
- Interfaces de Usuario (UI): Es la base de MVC/MVVM. El modelo cambia y la vista se actualiza sola.
- Sistemas de Eventos: Cuando una acción en un módulo debe disparar procesos en otros módulos desconocidos.
- Desacoplamiento: Cuando un objeto debe notificar a otros, pero no quieres que sepa quiénes son esos otros.
El patrón Observer es muy útil para crear aplicaciones reactivas y desacopladas. Permite que nuestros objetos “hablen” sin necesidad de conocerse íntimamente.