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 soporte para concurrencia segura utilizando colecciones apropiadas:
package tutorial.observer;
import java.time.Instant;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
public interface ObservadorPedido {
void notificar(EventoPedido evento);
}
public interface CentroEventosPedido {
void suscribir(ObservadorPedido observador);
void cancelar(ObservadorPedido observador);
void publicar(EventoPedido evento);
}
class CentroEventosPedidoImpl implements CentroEventosPedido {
private final List<ObservadorPedido> observadores = new CopyOnWriteArrayList<>();
@Override
public void suscribir(ObservadorPedido observador) {
observadores.add(Objects.requireNonNull(observador));
}
@Override
public void cancelar(ObservadorPedido observador) {
observadores.remove(observador);
}
@Override
public void publicar(EventoPedido evento) {
for (ObservadorPedido observador : observadores) {
observador.notificar(evento);
}
}
}
record EventoPedido(String idPedido, EstadoPedido estado, Instant timestamp, String origen) {
}
enum EstadoPedido {
CREADO, PAGADO, PREPARADO, ENVIADO, ENTREGADO, CANCELADO
}
class ObservadorLogistica implements ObservadorPedido {
@Override
public void notificar(EventoPedido evento) {
if (evento.estado() == EstadoPedido.PREPARADO) {
System.out.println("[Logística] Preparar retiro para " + evento.idPedido());
}
if (evento.estado() == EstadoPedido.ENVIADO) {
System.out.println("[Logística] Actualizar seguimiento del envío " + evento.idPedido());
}
}
}
class ObservadorMarketing implements ObservadorPedido {
@Override
public void notificar(EventoPedido evento) {
if (evento.estado() == EstadoPedido.ENVIADO) {
System.out.println("[Marketing] Enviar email de seguimiento a cliente para " + evento.idPedido());
}
if (evento.estado() == EstadoPedido.ENTREGADO) {
System.out.println("[Marketing] Solicitar calificación del pedido " + evento.idPedido());
}
}
}
class ObservadorAuditoria implements ObservadorPedido {
@Override
public void notificar(EventoPedido evento) {
System.out.printf("[Auditoría] Registro: %s estado=%s origen=%s%n",
evento.idPedido(), evento.estado(), evento.origen());
}
}
class ServicioPedidos {
private final CentroEventosPedido centroEventos;
ServicioPedidos(CentroEventosPedido centroEventos) {
this.centroEventos = centroEventos;
}
public void cambiarEstado(String idPedido, EstadoPedido estado, String origen) {
EventoPedido evento = new EventoPedido(idPedido, estado, Instant.now(), origen);
centroEventos.publicar(evento);
}
}
class AplicacionObserver {
public static void main(String[] args) {
CentroEventosPedido centroEventos = new CentroEventosPedidoImpl();
centroEventos.suscribir(new ObservadorLogistica());
centroEventos.suscribir(new ObservadorMarketing());
centroEventos.suscribir(new ObservadorAuditoria());
ServicioPedidos 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.
La decisión de usar CopyOnWriteArrayList
simplifica la concurrencia cuando las notificaciones son menos frecuentes que las lecturas. En escenarios de alto tráfico podría reemplazarse por colas no bloqueantes o usar ejecutores para notificar en forma asíncrona.
La documentación oficial de PropertyChangeSupport describe cómo la plataforma ofrece utilidades listas para usar, basadas en Observer, para escuchar cambios de propiedades. Bibliotecas como Jakarta EE, Spring y frameworks de interfaces (JavaFX, Swing) emplean implementaciones especializadas que permiten encadenar listeners, transformar eventos y propagar flujos reactivos.
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.