¡Claro! El término "supervidor" en Python se refiere a un patrón de diseño (design pattern) muy útil, también conocido como "Observer" en inglés. Es fundamental para crear sistemas desacoplados donde un objeto (el "sujeto" o "supervisado") necesita notificar automáticamente a otros objetos (los "observadores" o "supervisores") sobre cambios en su estado.
Imagina una suscripción a un canal de YouTube:
- Canal de YouTube (Sujeto/Subject): Es el objeto principal que tiene un estado (nuevos videos, cambios en la descripción, etc.).
- Tus Suscriptores (Observers): Son los objetos que quieren ser notificados cuando el canal sube un nuevo video.
- Notificación: Cuando el canal sube un video, notifica automáticamente a todos sus suscriptores.
En este patrón, el canal no necesita saber quiénes son los suscriptores, solo que debe notificar a todos los que se han registrado. Esto crea un bajo acoplamiento.
Componentes del Patrón Supervisor (Observer)
-
Sujeto (Subject / Observable): Es la interfaz o clase abstracta que define los métodos para gestionar los observadores.
attach(observer): Permite a un observador suscribirse.detach(observer): Permite a un observador darse de baja.notify(): Notifica a todos los observadores registrados sobre un cambio de estado.
-
Observador (Observer): Es la interfaz o clase abstracta que define el método que será llamado por el sujeto.
update(subject): Es el método que el observador implementa para reaccionar a la notificación.
-
Sujeto Concreto (Concrete Subject): Es la clase que implementa la lógica del sujeto. Mantiene una lista de observadores y su estado. Cuando su estado cambia, invoca el método
notify(). -
Observador Concreto (Concrete Observer): Es la clase que implementa la lógica del observador. Define qué acción debe realizar cuando recibe una notificación.
Ejemplo Práctico en Python
Vamos a implementar el ejemplo del "Clima" que es clásico para este patrón.
- Sujeto Concreto (
DatosClima): Un objeto que recibe datos de temperatura, humedad y presión. - Observadores Concretos (
DisplayCondicionesActuales,DisplayEstadisticas,ForecastDisplay): Pantallas que muestran información diferente basada en los datos del clima.
Paso 1: Definir las Interfaces (Clases Base)
Primero, creamos las clases base para el Sujeto y el Observador.
from abc import ABC, abstractmethod
# Observador: Define la interfaz de actualización para los objetos que deben ser notificados.
class Observador(ABC):
@abstractmethod
def actualizar(self, temperatura, humedad, presion):
pass
# Sujeto: Mantiene una lista de observadores y los notifica de los cambios.
class Sujeto(ABC):
@abstractmethod
def registrar_observador(self, observador: Observador):
pass
@abstractmethod
def eliminar_observador(self, observador: Observador):
pass
@abstractmethod
def notificar_observadores(self):
pass
Paso 2: Implementar el Sujeto Concreto
Esta clase guardará los datos del clima y notificará a los observadores cuando cambien.
# Sujeto Concreto: Implementa la lógica del Sujeto.
class DatosClima(Sujeto):
def __init__(self):
self._observadores = []
self._temperatura = 0.0
self._humedad = 0.0
self._presion = 1013.2
def registrar_observador(self, observador: Observador):
self._observadores.append(observador)
def eliminar_observador(self, observador: Observador):
# La forma más segura es manejar el caso en que el observador no esté
if observador in self._observadores:
self._observadores.remove(observador)
def notificar_observadores(self):
for observador in self._observadores:
observador.actualizar(self._temperatura, self._humedad, self._presion)
# Métodos para cambiar el estado y notificar automáticamente
def medir_cambios(self, temperatura, humedad, presion):
print("\nActualizando mediciones del clima...")
self._temperatura = temperatura
self._humedad = humedad
self._presion = presion
self.notificar_observadores()
Paso 3: Implementar los Observadores Concretos
Cada observador reaccionará a la notificación de una manera diferente.
# Observador Concreto 1: Muestra las condiciones actuales.
class DisplayCondicionesActuales(Observador):
def actualizar(self, temperatura, humedad, presion):
print("--- Condiciones Actuales ---")
print(f"Temperatura: {temperatura}°C")
print(f"Humedad: {humedad}%")
print("---------------------------")
# Observador Concreto 2: Muestra estadísticas simples.
class DisplayEstadisticas(Observador):
def __init__(self):
self._temperaturas = []
def actualizar(self, temperatura, humedad, presion):
self._temperaturas.append(temperatura)
if len(self._temperaturas) > 5: # Mantener solo las últimas 5
self._temperaturas.pop(0)
promedio = sum(self._temperaturas) / len(self._temperaturas)
print("--- Estadísticas de Temperatura ---")
print(f"Temperatura Promedio (últimas 5 lecturas): {promedio:.2f}°C")
print("------------------------------------")
# Observador Concreto 3: Un pronóstico simple.
class ForecastDisplay(Observador):
def __init__(self):
self._prev_temp = 0.0
def actualizar(self, temperatura, humedad, presion):
self._prev_temp = temperatura
if temperatura > self._prev_temp:
pronostico = "Mejorando: ¡El clima se calentará!"
elif temperatura < self._prev_temp:
pronostico = "Atención: ¡El clima se enfriará!"
else:
pronostico = "Sin cambios: El clima se mantendrá estable."
print("--- Pronóstico ---")
print(pronostico)
print("-----------------")
Paso 4: Poner Todo Junto (El Programa Principal)
Ahora, conectamos todo para que funcione.
if __name__ == "__main__":
# 1. Crear el Sujeto (la fuente de datos)
datos_clima = DatosClima()
# 2. Crear los Observadores (las pantallas que mostrarán los datos)
display_actuales = DisplayCondicionesActuales()
display_estadisticas = DisplayEstadisticas()
display_pronostico = ForecastDisplay()
# 3. Registrar los observadores en el sujeto
datos_clima.registrar_observador(display_actuales)
datos_clima.registrar_observador(display_estadisticas)
datos_clima.registrar_observador(display_pronostico)
# 4. Simular cambios en los datos del clima
# Los observadores serán notificados automáticamente
datos_clima.medir_cambios(25.5, 65, 1015.1)
datos_clima.medir_cambios(26.8, 70, 1014.5)
datos_clima.medir_cambios(24.2, 90, 1016.3)
# 5. Podemos eliminar un observador si ya no nos interesa
print("\nEliminando el pronóstico de la lista de notificaciones...")
datos_clima.eliminar_observador(display_pronostico)
# 6. Realizar otro cambio para ver que el pronóstico ya no se actualiza
datos_clima.medir_cambios(23.1, 85, 1017.0)
Salida del Programa
Actualizando mediciones del clima...
--- Condiciones Actuales ---
Temperatura: 25.5°C
Humedad: 65%
---------------------------
--- Estadísticas de Temperatura ---
Temperatura Promedio (últimas 5 lecturas): 25.50°C
------------------------------------
--- Pronóstico ---
Mejorando: ¡El clima se calentará!
-----------------
Actualizando mediciones del clima...
--- Condiciones Actuales ---
Temperatura: 26.8°C
Humedad: 70%
---------------------------
--- Estadísticas de Temperatura ---
Temperatura Promedio (últimas 5 lecturas): 26.15°C
------------------------------------
--- Pronóstico ---
Mejorando: ¡El clima se calentará!
-----------------
Actualizando mediciones del clima...
--- Condiciones Actuales ---
Temperatura: 24.2°C
Humedad: 90%
---------------------------
--- Estadísticas de Temperatura ---
Temperatura Promedio (últimas 5 lecturas): 25.50°C
------------------------------------
--- Pronóstico ---
Atención: ¡El clima se enfriará!
-----------------
Eliminando el pronóstico de la lista de notificaciones...
Actualizando mediciones del clima...
--- Condiciones Actuales ---
Temperatura: 23.1°C
Humedad: 85%
---------------------------
--- Estadísticas de Temperatura ---
Temperatura Promedio (últimas 5 lecturas): 24.70°C
------------------------------------
Ventajas del Patrón Supervisor/Observer
- Desacoplamiento: El Sujeto no conoce los detalles de los Observadores. Solo sabe que existen y que deben ser notificados. Esto facilita la modificación y adición de nuevos observadores sin tocar el código del sujeto.
- Comunicación Broadcast: Permite una comunicación de uno-a-muchos de forma eficiente.
- Separación de Responsabilidades: Cada objeto tiene una única responsabilidad bien definida.
Implementación Integrada en Python (sin abc)
Para casos más simples, no es necesario usar las clases abstractas (ABC). Python es muy dinámico y podemos lograr el mismo resultado de una manera más "pythónica".
class SujetoSimple:
def __init__(self):
self._observadores = []
def registrar(self, observador):
if observador not in self._observadores:
self._observadores.append(observador)
def eliminar(self, observador):
try:
self._observadores.remove(observador)
except ValueError:
pass
def notificar(self, *args, **kwargs):
for observador in self._observadores:
observador(*args, **kwargs)
class ObservadorSimple:
def __init__(self, nombre):
self.nombre = nombre
def __call__(self, mensaje):
print(f"[{self.nombre}] Recibió: {mensaje}")
# Uso
sujeto = SujetoSimple()
obs1 = ObservadorSimple("Notificación 1")
obs2 = ObservadorSimple("Alerta de Sistema")
sujeto.registrar(obs1)
sujeto.registrar(obs2)
sujeto.notificar("¡El sistema está operativo!")
# Salida:
# [Notificación 1] Recibió: ¡El sistema está operativo!
# [Alerta de Sistema] Recibió: ¡El sistema está operativo!
sujeto.eliminar(obs1)
sujeto.notificar("Advertencia: sobrecarga detectada.")
# Salida:
# [Alerta de Sistema] Recibió: Advertencia: sobrecarga detectada.
¿Dónde se usa este patrón en Python?
Aunque no siempre se le llame "supervisor", el concepto está por todas partes:
- Sistemas de Eventos: GUI (PyQt, Tkinter), frameworks web (Django Signals), donde un evento (clic de un botón) notifica a una función controladora.
- Patrones MVC (Modelo-Vista-Controlador): El Modelo (sujeto) notifica a las Vistas (observadores) cuando los datos cambian.
- Patrones MVVM (Modelo-Vista-ViewModel): Similar a MVC, el ViewModel notifica a la Vista sobre cambios.
- Bibliotecas de Logging: Un logger (sujeto) puede enviar logs a múltiples handlers (observadores) como consola, archivo, etc.
En resumen, el patrón supervisor (observer) es una herramienta poderosa para construir sistemas reactivos, flexibles y fáciles de mantener en Python.
