Patrón Observer
Define un mecanismo de suscripción para notificar a múltiples objetos sobre eventos. Patrón de diseño conductual para comunicación basada en eventos.
Visión general
El Patrón Observer es un patrón de diseño conductual que define un mecanismo de suscripción para notificar a múltiples objetos sobre eventos que ocurren en el objeto que están observando. Establece una dependencia uno-a-muchos entre objetos.
Es la base de arquitecturas basadas en eventos, programación reactiva y la arquitectura Model-View en frameworks de UI.
Cuándo usarlo
Usa el Patrón Observer cuando:
- Los cambios en un objeto requieren actualizar un número desconocido de objetos dependientes. Consulta Mediator Pattern para enrutamiento centralizado.
- Necesitas un modelo de comunicación publicar-suscribir. Consulta CQRS Pattern para arquitecturas event-driven.
- Un objeto debe notificar a otros sin saber quiénes son
- Quieres acoplamiento débil entre productores y consumidores de eventos
- Construyes componentes de UI reactivos o feeds de datos en tiempo real. Consulta API REST para fetching de datos en tiempo real.
Solución
Python
class Subject:
def __init__(self):
self._observers = []
def attach(self, observer):
self._observers.append(observer)
def notify(self, data):
for observer in self._observers:
observer.update(data)
class Observer:
def update(self, data):
print(f"Recibido: {data}")
# Uso
subject = Subject()
subject.attach(Observer())
subject.attach(Observer())
subject.notify("¡Hola observadores!")
JavaScript
class Subject {
constructor() {
this.observers = [];
}
subscribe(fn) {
this.observers.push(fn);
}
notify(data) {
this.observers.forEach((fn) => fn(data));
}
}
// Uso
const subject = new Subject();
subject.subscribe((data) => console.log("A:", data));
subject.subscribe((data) => console.log("B:", data));
subject.notify("¡Hola observadores!");
Java
import java.util.ArrayList;
import java.util.List;
interface Observer {
void update(String data);
}
class Subject {
private final List<Observer> observers = new ArrayList<>();
void attach(Observer o) {
observers.add(o);
}
void notifyObservers(String data) {
for (Observer o : observers) {
o.update(data);
}
}
}
// Uso
Subject subject = new Subject();
subject.attach(data -> System.out.println("Recibido: " + data));
subject.notifyObservers("¡Hola observadores!");
Explicación
El Patrón Observer consiste en dos roles principales:
- Subject (Publicador): Mantiene una lista de observadores y envía notificaciones
- Observer (Suscriptor): Define una interfaz para objetos que deben ser notificados de cambios
Cuando el estado del Subject cambia, itera sobre sus observadores y llama su método update. Los observadores pueden suscribirse o darse de baja dinámicamente sin que el Subject conozca las clases concretas.
Variantes
| Variante | Caso de uso | Compromiso |
|---|---|---|
| Modelo push | El subject envía datos completos a observadores | Simple, pero puede enviar datos innecesarios |
| Modelo pull | El subject notifica; los observadores consultan detalles | Más eficiente, pero añade idas y vueltas |
| Event bus | Un despachador central desacopla subjects y observers | Más flexible, añade indirección |
Lo que funciona
- Desuscríbete de observadores cuando se destruyen para prevenir fugas de memoria
- Evita actualizaciones circulares donde observadores disparan cambios de vuelta al subject
- Usa referencias débiles en lenguajes que lo soportan (ej. Java) para limpieza automática
- Mantén la lógica de notificación simple y evita cómputos pesados en el loop de notify
- Documenta los payloads de eventos para que los observadores sepan qué datos esperar
Errores comunes
- Fugas de memoria: Olvidar desvincular observadores cuando ya no se necesitan
- Orden de actualización inesperado: Los observadores pueden ejecutarse en orden indefinido; no dependas de ello
- Bucles infinitos: Un observador que modifica el subject puede desencadenar actualizaciones en cascada
- Acoplamiento fuerte: Dar a los observadores acceso al subject completo en lugar de solo los datos que necesitan
- Bloqueo síncrono: Ejecutar observadores lentos en el hilo principal de notificación
Notas de Producción
- Despliega gradualmente usando canary o blue-green para detectar regresiones temprano.
- Configura alertas para errores, latencia p99 y tasa de fallos antes de habilitar en producción.
- Documenta el rollback en el runbook; prueba el procedimiento en staging al menos una vez por trimestre.
- Revisa logs estructurados con correlation IDs para trazar requests end-to-end en incidentes.
Puntos Clave
- Aplica patrón observer cuando necesites una solución práctica para tu caso de uso.
- Monitorea el rendimiento después de implementar; mide latencia, errores y uso de recursos antes y después.
- Revisa la sección de Troubleshooting ante errores comunes; la mayoría tienen causa raíz documentada con solución.
- Mantén dependencias actualizadas y ejecuta tests en CI para prevenir regresiones en producción.
Temas Avanzados
Escenario: Sistema de Eventos con Observer Pattern
// Observer pattern para sistema de notificaciones
interface Observer {
update(event: string, data: unknown): void;
}
class EventEmitter {
private observers: Map<string, Set<Observer>> = new Map();
subscribe(event: string, observer: Observer): void {
if (!this.observers.has(event)) {
this.observers.set(event, new Set());
}
this.observers.get(event)!.add(observer);
}
unsubscribe(event: string, observer: Observer): void {
this.observers.get(event)?.delete(observer);
}
emit(event: string, data: unknown): void {
this.observers.get(event)?.forEach(obs => {
try {
obs.update(event, data);
} catch (err) {
console.error(`Observer error: ${err}`);
}
});
}
}
// Uso: sistema de e-commerce
const emitter = new EventEmitter();
// Observers
class EmailNotifier implements Observer {
update(event: string, data: unknown): void {
if (event === "order.created") {
sendEmail((data as Order).userEmail, "Order confirmed");
}
}
}
class InventoryUpdater implements Observer {
update(event: string, data: unknown): void {
if (event === "order.created") {
decrementStock((data as Order).items);
}
}
}
class AnalyticsTracker implements Observer {
update(event: string, data: unknown): void {
trackEvent(event, data);
}
}
// Subscribe
emitter.subscribe("order.created", new EmailNotifier());
emitter.subscribe("order.created", new InventoryUpdater());
emitter.subscribe("order.created", new AnalyticsTracker());
// Emit
emitter.emit("order.created", { id: "123", userEmail: "user@example.com", items: [...] });
// Resultado: email enviado, stock actualizado, analytics tracked
Lecciones:
- Observer desacopla el emisor de los receptores
- Cada observer es independiente: fallo en uno no afecta otros
- Usar Set para evitar duplicados
- Try-catch en emit: un observer roto no rompe el evento
- Para sistemas distribuidos, usar message broker (RabbitMQ, Kafka)
- Memory leak: unsubscribe cuando el observer ya no es necesario
### Como evito memory leaks con observers?
Siempre llama unsubscribe cuando el observer ya no es necesario. En React, usa useEffect cleanup: subscribe en mount, unsubscribe en unmount. En Node.js, usa WeakRef o limpia explicitamente en shutdown. Si observers crecen sin limite, usa un Map con TTL o un max de observers por evento.
End of document. Review and update quarterly.
## Troubleshooting
- **Pattern does not fit the problem**: re-evaluate the forces (performance, scalability, team size, coupling). A pattern is only appropriate when its trade-offs match your constraints.
- **Too many abstractions**: if adding a pattern increases complexity without a clear benefit, simplify. Not every module needs a factory, decorator, or strategy.
- **Tight coupling after refactoring**: check that interfaces are stable and dependencies point inward.
- **Tests break when the design changes**: favor stable contracts over internal structure.
- **Performance regression from indirection**: measure before and after. Layers, decorators, and adapters can add latency; cache or inline hot paths if needed.
## Errores Comunes en Producción
- Aplicar el patrón donde no se necesita abstracción, agregando complejidad accidental.
- Dejar que el patrón se filtre en módulos no relacionados y confundir los límites de responsabilidad.
- Sobre-ingeniería en la primera implementación en lugar de comenzar simple y medir el dolor.
- Saltar los tests de contrato, de modo que las refactorizaciones rompan consumidores en silencio.
- Ignorar modos de fallo que el patrón no cubre.
- Usar el patrón como opción por defecto en lugar de elegir la herramienta adecuada para la escala actual.
- Olvidar documentar cuándo dejar de usar el patrón y qué lo reemplaza.
- Carecer de observabilidad sobre rendimiento y propagación de errores del patrón. Preguntas frecuentes
¿Es este patrón adecuado para proyectos pequeños?
Para proyectos pequeños con pocos componentes, este patrón puede añadir complejidad innecesaria. Empieza simple e introduce el patrón cuando sientas el problema que resuelve.
¿Cómo se compara este patrón con alternativas?
Cada patrón hace diferentes trade-offs. Revisa la tabla de variantes arriba y considera tus restricciones específicas: tamaño del equipo, requisitos de rendimiento y planes de escalado.
¿Puedo aplicar este patrón parcialmente?
Sí. Muchos equipos adoptan patrones incrementalmente. Empieza con la idea central y añade sofisticación según sea necesario. El patrón es una guía, no un blueprint estricto.
Recursos Relacionados
Llamar a una API REST: Python, JS, Java y Go
Cómo hacer peticiones HTTP a una API REST y manejar la respuesta JSON en Python, JavaScript, Java y Go.
PatternPatrón Singleton
Garantiza que una clase tenga una única instancia y proporciona un acceso global a ella. Patrón de diseño creacional para controlar la creación de objetos.
PatternPatrón Strategy
Define una familia de algoritmos, encapsula cada uno y los hace intercambiables. Patrón de diseño conductual para selección flexible de comportamiento.
PatternPatrón Pipes and Filters: Pipelines de Datos Componibles
Encadena filtros independientes con pipes para construir pipelines de transformación de datos reutilizables y componibles.
PatternPatrón Blackboard
Un espacio de conocimiento compartido donde módulos especializados independientes colaboran para resolver problemas complejos contribuyendo soluciones parciales.
PatternPatrón Circuit Breaker
Previene fallos en cascada deteniendo solicitudes a servicios que están fallando. Un patrón arquitectural para sistemas distribuidos resilientes.