¿Te resulta más fácil leer con fondo crema, un tono de color, más contraste o un texto más grande? Elige tus colores y tu texto una sola vez, para todas las páginas.

Lección gratuita

Patrones: observador y estado

Después de esta lección: Puedo aplicar los patrones observador y estado.

Lección de 75 min5 comandosLaboratorio de comandos de Linux de cada día: los treinta comandos que se escriben de memoria

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

PalabraQué significa
Observadorun objeto que quiere saber cuándo cambia otro
Sujetoel objeto observado, que guarda su lista de observadores
Suscribirapuntar un observador para que reciba los avisos
Notificaravisar a todos los observadores de un cambio
Estadola 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

PasoQué ocurreMensajes de AnaMensajes de Luis
1Ana se suscribeningunoninguno
2Luis se suscribeningunoninguno
3El tablón publica «Examen el lunes»Examen el lunesExamen el lunes

Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.

Un tablón y dos alumnos suscritos

Código: python
# 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

Comando
mkdir -p ~/oop && cd ~/oop

Se 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

Comando
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.py

Se 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

Comando
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 actualTexto que muestraEstado siguiente
PendientePendiente de pagoPagado
PagadoPagadoEnviado
EnviadoEnviadoEnviado, ya no cambia

Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.

Un pedido que avanza de estado

Código: python
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

Comando
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.py

Se 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

Comando
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.

Sobre este laboratorio y su código: Laboratorio de comandos de Linux de cada día: los treinta comandos que se escriben de memoria

¿Enseñas a una clase? Asigna este módulo a tus alumnos con guía del profesor, tareas con corrección automática y un laboratorio para cada alumno.