Avisar a muchos y cambiar de comportamiento
El patrón observador avisa a muchos objetos cuando otro objeto cambia. El patrón estado hace que un objeto cambie de comportamiento según la situación en que está.
Un tablón de anuncios avisa a todo el alumnado suscrito. Un pedido pasa de pendiente a pagado y a enviado, y en cada fase responde distinto.
Al final de esta lección
Nota
Puedo aplicar los patrones observador y estado.
Palabras clave
| Palabra | Qué significa |
|---|---|
| Observador | un objeto que quiere saber cuándo cambia otro |
| Sujeto | el objeto observado, que guarda su lista de observadores |
| Suscribir | apuntar un observador para que reciba los avisos |
| Notificar | avisar a todos los observadores de un cambio |
| Estado | la fase en que está un objeto, con su comportamiento |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
El observador avisa a quien se ha suscrito
El sujeto guarda una lista de observadores. Cada observador se suscribe. Cuando algo cambia, el sujeto recorre la lista y llama a un método de cada uno. Eso es notificar.
El sujeto no sabe quién escucha ni qué hace con el aviso. Solo sabe que cada uno tiene el método recibir.
Ejemplo: un tablón con dos suscritos
| Paso | Qué ocurre | Mensajes de Ana | Mensajes de Luis |
|---|---|---|---|
| 1 | Ana se suscribe | ninguno | ninguno |
| 2 | Luis se suscribe | ninguno | ninguno |
| 3 | El tablón publica «Examen el lunes» | Examen el lunes | Examen el lunes |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Un tablón y dos alumnos suscritos
# Un tablón avisa a quien se ha suscrito.
class Tablon:
def __init__(self):
self.oyentes = []
def suscribir(self, oyente):
self.oyentes.append(oyente)
def publicar(self, mensaje):
for oyente in self.oyentes:
oyente.recibir(mensaje)
class Alumno:
def __init__(self, nombre):
self.nombre = nombre
self.mensajes = []
def recibir(self, mensaje):
self.mensajes.append(mensaje)
tablon = Tablon()
ana = Alumno("Ana")
luis = Alumno("Luis")
tablon.suscribir(ana)
tablon.suscribir(luis)
tablon.publicar("Examen el lunes")
print(ana.mensajes, luis.mensajes)
Prueba el tablón en el laboratorio
Guarda las dos clases en tablon.py y úsalas desde la terminal. Los dos alumnos se suscriben y el tablón publica un solo mensaje.
Predice qué listas imprimirá la orden antes de pulsar Ejecutar.
Entra en la carpeta de trabajo
mkdir -p ~/oop && cd ~/oopSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Escribe el tablón y el alumno
printf 'class Tablon:\n def __init__(self):\n self.oyentes = []\n\n def suscribir(self, oyente):\n self.oyentes.append(oyente)\n\n def publicar(self, mensaje):\n for oyente in self.oyentes:\n oyente.recibir(mensaje)\n\n\nclass Alumno:\n def __init__(self, nombre):\n self.nombre = nombre\n self.mensajes = []\n\n def recibir(self, mensaje):\n self.mensajes.append(mensaje)\n' > tablon.pySe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Suscribe a dos alumnos y publica un aviso
python3 -c "from tablon import Tablon, Alumno; t = Tablon(); ana = Alumno('Ana'); luis = Alumno('Luis'); t.suscribir(ana); t.suscribir(luis); t.publicar('Examen el lunes'); print(ana.mensajes, luis.mensajes)"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
El estado cambia el comportamiento del objeto
Un pedido pasa por fases: pendiente de pago, pagado y enviado. En cada fase hace cosas distintas.
En el patrón estado, cada fase es una clase con su propio comportamiento. El pedido guarda el estado actual y se lo pide todo. Para cambiar de fase, cambia el objeto de estado.
Ejemplo: las fases de un pedido
| Estado actual | Texto que muestra | Estado siguiente |
|---|---|---|
| Pendiente | Pendiente de pago | Pagado |
| Pagado | Pagado | Enviado |
| Enviado | Enviado | Enviado, ya no cambia |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Un pedido que avanza de estado
class Pendiente:
def siguiente(self):
return Pagado()
def texto(self):
return "Pendiente de pago"
class Pagado:
def siguiente(self):
return Enviado()
def texto(self):
return "Pagado"
class Enviado:
def siguiente(self):
return self
def texto(self):
return "Enviado"
class Pedido:
def __init__(self):
self.estado = Pendiente()
def avanzar(self):
self.estado = self.estado.siguiente()
pedido = Pedido()
print(pedido.estado.texto())
pedido.avanzar()
print(pedido.estado.texto())
Un error frecuente
Advertencia
Mucha gente cree que basta guardar el estado en un texto y mirarlo con if. Con dos estados funciona. Con cinco, cada método se llena de if y cada estado nuevo obliga a editarlos todos. Con una clase por estado, un estado nuevo es una clase nueva.
Escribe los tres estados y el pedido
printf 'class Pendiente:\n def siguiente(self):\n return Pagado()\n\n def texto(self):\n return "Pendiente de pago"\n\n\nclass Pagado:\n def siguiente(self):\n return Enviado()\n\n def texto(self):\n return "Pagado"\n\n\nclass Enviado:\n def siguiente(self):\n return self\n\n def texto(self):\n return "Enviado"\n\n\nclass Pedido:\n def __init__(self):\n self.estado = Pendiente()\n\n def avanzar(self):\n self.estado = self.estado.siguiente()\n' > estados.pySe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Avanza el pedido tres veces
python3 -c "from estados import Pedido; p = Pedido(); print(p.estado.texto()); p.avanzar(); print(p.estado.texto()); p.avanzar(); p.avanzar(); print(p.estado.texto())"Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.