Saltar al contenido principal

Observer

Imagina la siguiente situación: tienes una tienda online y quieres avisar a tus clientes cuando un producto vuelva a estar disponible. ¿Cómo lo harías?

Una solución torpe sería que cada cliente entrase a la web cada cinco minutos a comprobar si el producto ya está disponible. Esto es ineficiente y molesto.

Una solución mucho mejor: el cliente se suscribe a una notificación, y cuando el producto esté disponible, la tienda le avisa automáticamente.

Exactamente esto es lo que resuelve el patrón Observer.

¿En qué consiste el patrón Observer?

El patrón Observer establece una relación entre dos tipos de objetos:

  • El Sujeto (también llamado Observable o Publisher): es el objeto que tiene información relevante y que puede cambiar de estado. Cuando cambia, notifica a otros.
  • Los Observadores (también llamados Listeners o Subscribers): son los objetos que están interesados en los cambios del Sujeto. Cuando el Sujeto cambia, ellos reaccionan.

Esta relación se llama "uno a muchos": un único Sujeto puede tener muchos Observadores suscritos.

El flujo es siempre el mismo:

  1. Los Observadores se suscriben al Sujeto.
  2. El Sujeto cambia de estado en algún momento.
  3. El Sujeto notifica automáticamente a todos sus Observadores.
  4. Cada Observador reacciona a su manera ante el cambio.

Ejemplo práctico

Vamos a modelar el ejemplo de la tienda online. Tenemos un producto que puede quedarse sin stock, y varios tipos de clientes que quieren ser avisados cuando vuelva a estar disponible.

Paso 1 — Definir la interfaz del Observador

Todos los observadores deben tener un método común para recibir notificaciones. Lo definimos en una interfaz:

// Cualquier clase que quiera "observar" debe implementar esta interfaz
public interface Observador {
void actualizar(String nombreProducto);
}

Paso 2 — Definir la interfaz del Sujeto

El Sujeto debe permitir que los Observadores se suscriban, se desuscriban y ser notificados:

public interface Sujeto {
void suscribir(Observador observador);
void desuscribir(Observador observador);
void notificarObservadores();
}

Paso 3 — Implementar el Sujeto: la clase Producto

import java.util.ArrayList;
import java.util.List;

public class Producto implements Sujeto {

private String nombre;
private boolean enStock;
private List<Observador> observadores = new ArrayList<>();

public Producto(String nombre) {
this.nombre = nombre;
this.enStock = false;
}

@Override
public void suscribir(Observador observador) {
observadores.add(observador);
System.out.println("✅ Nuevo observador suscrito al producto: " + nombre);
}

@Override
public void desuscribir(Observador observador) {
observadores.remove(observador);
System.out.println("❌ Observador desuscrito del producto: " + nombre);
}

@Override
public void notificarObservadores() {
for (Observador observador : observadores) {
observador.actualizar(nombre);
}
}

// Cuando el stock cambia, notificamos a todos
public void setEnStock(boolean enStock) {
this.enStock = enStock;
if (this.enStock) {
System.out.println("\n📦 ¡El producto \"" + nombre + "\" ya está disponible!");
notificarObservadores();
}
}
}

Paso 4 — Implementar los Observadores

Podemos tener distintos tipos de observadores. Cada uno reacciona de forma diferente:

// Cliente que recibe una notificación por email
public class ClienteEmail implements Observador {

private String email;

public ClienteEmail(String email) {
this.email = email;
}

@Override
public void actualizar(String nombreProducto) {
System.out.println("📧 Email enviado a " + email
+ ": El producto \"" + nombreProducto + "\" ya está disponible.");
}
}
// Cliente que recibe una notificación por SMS
public class ClienteSMS implements Observador {

private String telefono;

public ClienteSMS(String telefono) {
this.telefono = telefono;
}

@Override
public void actualizar(String nombreProducto) {
System.out.println("📱 SMS enviado al " + telefono
+ ": ¡\"" + nombreProducto + "\" vuelve a estar en stock!");
}
}

Paso 5 — Probarlo todo junto

public class Main {
public static void main(String[] args) {

// Creamos el producto (el Sujeto)
Producto teclado = new Producto("Teclado mecánico RGB");

// Creamos los clientes (los Observadores)
Observador cliente1 = new ClienteEmail("ana@correo.com");
Observador cliente2 = new ClienteEmail("luis@correo.com");
Observador cliente3 = new ClienteSMS("+34 612 345 678");

// Los clientes se suscriben al producto
teclado.suscribir(cliente1);
teclado.suscribir(cliente2);
teclado.suscribir(cliente3);

// Luis se arrepiente y se desuscribe
teclado.desuscribir(cliente2);

// El producto vuelve a estar en stock → se notifica a los suscritos
teclado.setEnStock(true);
}
}

Salida por consola:

✅ Nuevo observador suscrito al producto: Teclado mecánico RGB
✅ Nuevo observador suscrito al producto: Teclado mecánico RGB
✅ Nuevo observador suscrito al producto: Teclado mecánico RGB
❌ Observador desuscrito del producto: Teclado mecánico RGB

📦 ¡El producto "Teclado mecánico RGB" ya está disponible!
📧 Email enviado a ana@correo.com: El producto "Teclado mecánico RGB" ya está disponible.
📱 SMS enviado al +34 612 345 678: ¡"Teclado mecánico RGB" vuelve a estar en stock!

Luis no recibe ninguna notificación porque se desuscribió. Ana y el cliente de SMS sí la reciben.

Ventajas y desventajas

Ventajas:

  • Desacoplamiento: El Sujeto no necesita saber qué hace cada Observador. Solo los notifica.
  • Flexibilidad: Puedes añadir o quitar Observadores en tiempo de ejecución sin tocar el Sujeto.
  • Extensibilidad: Crear un nuevo tipo de notificación (por ejemplo, push notification) solo requiere crear una nueva clase que implemente Observador.
  • Principio abierto/cerrado: El código existente no se modifica al añadir nuevos observadores.

Desventajas:

  • Si hay muchos observadores, las notificaciones pueden volverse lentas o generar un efecto en cadena difícil de seguir.
  • El orden en que se notifica a los observadores puede no ser predecible.
  • Si un observador no se desuscribe correctamente, puede producir fugas de memoria.

¿Dónde se usa?

El patrón Observer está por todas partes:

  • Interfaces gráficas (UI): cuando haces clic en un botón, el botón notifica a sus "listeners" que han sido pulsados.
  • Frameworks de eventos: Spring, Angular, React... todos usan variantes de este patrón.
  • Sistemas de mensajería: Kafka, RabbitMQ. Los consumidores son observadores de un topic.
  • Redes sociales: cuando alguien publica algo, todos sus seguidores (observadores) reciben la notificación.
  • Hojas de cálculo: cuando cambias el valor de una celda, todos los gráficos y fórmulas que dependen de ella se actualizan automáticamente.