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.
Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.
Patrón Observer
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
Preguntas frecuentes
P: ¿Cuál es la diferencia entre Observer y Pub/Sub? R: Observer es una relación directa subject-observer. Pub/Sub añade un broker de eventos (Mediator) que desacopla completamente a los publicadores de los suscriptores.
P: ¿Sigue siendo relevante el Patrón Observer con frameworks reactivos modernos? R: Sí. React hooks, RxJS y el sistema de reactividad de Vue están construidos sobre conceptos de Observer. Para brokers de eventos singleton, consulta Singleton.
P: ¿Cómo evito fugas de memoria con observadores? R: Siempre proporciona un mecanismo de desuscripción y llámalo en manejadores de limpieza o destructores.
¿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.
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. Recursos Relacionados
Call a REST API
How to make HTTP requests to a REST API and handle the JSON response in multiple languages.
PatternSingleton Pattern
Ensure a class has only one instance and provide global access to it. A creational design pattern for controlled object creation.
PatternStrategy Pattern
Define a family of algorithms, encapsulate each one, and make them interchangeable. A behavioral design pattern for flexible behavior selection.
PatternPipes and Filters Pattern
Chain processing steps with independent filters connected by pipes. A pattern for data transformation pipelines where each step is reusable and composable.
PatternBlackboard Pattern
A shared knowledge space where independent specialized modules collaborate to solve complex problems by contributing partial solutions.
PatternCircuit Breaker Pattern
Prevent cascading failures by stopping requests to failing services. An architectural pattern for resilient distributed systems.
PatternCommand Pattern
Encapsulate a request as an object, letting you parameterize clients with queues, logs, and undoable operations. A behavioral design pattern.