Lección 8.2 : Patron de diseño State
Patrón de Diseño State en Python
Introducción
¡Hola a todos! Soy Yuscu Mejía y en esta lección vamos a explorar el concepto del patrón de diseño State en Python. El patrón State permite a un objeto alterar su comportamiento cuando su estado interno cambia. El objeto parecerá cambiar su clase.
Características
El patrón State en Python tiene las siguientes características:
- Permite a un objeto cambiar su comportamiento cuando cambia su estado.
- Encapsula los estados en clases separadas.
- El contexto delega las solicitudes a su estado actual.
- Facilita el mantenimiento y extensión de código al seguir el principio abierto/cerrado.
Propósito
El patrón de diseño State permite a un objeto alterar su comportamiento cuando su estado interno cambia. El objeto parecerá cambiar su clase.
Problema
El problema que resuelve el patrón State es el de permitir que un objeto cambie su comportamiento cuando cambia su estado, sin necesidad de usar numerosos condicionales.
Solución
La solución propuesta por el patrón State es encapsular los estados en clases separadas y permitir que el objeto cambie entre estos estados a medida que cambia su situación interna.
Analogía en el Mundo Real
Una analogía del mundo real sería un reproductor de música. Dependiendo del estado (reproduciendo, pausado, detenido), el reproductor responde de manera diferente a la entrada del usuario (como presionar el botón de reproducción o pausa).
Estructura
La estructura del patrón State incluye los siguientes componentes:
- State: Interfaz común para todos los estados.
- ConcreteState: Implementaciones concretas de la interfaz State.
- Context: Mantiene una referencia a un objeto State y delega las solicitudes a su estado actual.

Aplicabilidad
El patrón State es aplicable cuando:
- Se tiene un objeto cuyo comportamiento cambia en función de su estado.
- Se desea evitar el uso de numerosos condicionales para manejar el comportamiento en función del estado.
- Se desea poder cambiar el estado del objeto en tiempo de ejecución de manera controlada.
Pros y Contras
- Pros:
- Permite cambiar el comportamiento del objeto en tiempo de ejecución.
- Promueve la separación de responsabilidades al encapsular comportamientos específicos en clases de estado.
- Facilita la adición de nuevos estados sin modificar el contexto.
- Contras:
- Introduce complejidad adicional debido a la creación de múltiples clases de estado.
- El contexto necesita ser consciente de los posibles estados y cómo utilizarlos.
Vamos a ver uso Patron State vs Programación Estructural con condicional
A continuación, un ejemplo de cómo utilizar el patrón State en Python, presentados por Yuscu Mejía:
- Patron Strategy:
# Definir la interfaz Strategy
from abc import ABC, abstractmethod
class Estado(ABC):
def __init__(self, juguete):
self.juguete = juguete
@abstractmethod
def presionar_boton_encender(self):
pass
@abstractmethod
def presionar_boton_jugar(self):
pass
@abstractmethod
def presionar_boton_detener(self):
pass
# Definir estrategias concretas
class EstadoApagado(Estado):
def presionar_boton_encender(self):
print("El juguete está listo para jugar.")
self.juguete.cambiar_estado(EstadoListo(self.juguete))
def presionar_boton_jugar(self):
print("El juguete está apagado. Enciéndelo primero.")
def presionar_boton_detener(self):
print("El juguete está apagado.")
class EstadoListo(Estado):
def presionar_boton_encender(self):
print("El juguete está jugando.")
self.juguete.cambiar_estado(EstadoJugando(self.juguete))
def presionar_boton_jugar(self):
print("El juguete está jugando.")
self.juguete.cambiar_estado(EstadoJugando(self.juguete))
def presionar_boton_detener(self):
print("El juguete está listo, pero no está jugando.")
class EstadoJugando(Estado):
def presionar_boton_encender(self):
print("El juguete está apagado.")
self.juguete.cambiar_estado(EstadoApagado(self.juguete))
def presionar_boton_jugar(self):
print("El juguete ya está jugando.")
def presionar_boton_detener(self):
print("El juguete ha dejado de jugar.")
self.juguete.cambiar_estado(EstadoListo(self.juguete))
# Definir el contexto que utiliza una Strategy
class Juguete:
def __init__(self):
self.estado = EstadoApagado(self) # El juguete empieza apagado
def cambiar_estado(self, estado):
self.estado = estado
def presionar_boton_encender(self):
self.estado.presionar_boton_encender()
def presionar_boton_jugar(self):
self.estado.presionar_boton_jugar()
def presionar_boton_detener(self):
self.estado.presionar_boton_detener()
def get_estado(self):
return self.estado.__class__.__name__
# Ejemplo de uso
if __name__ == "__main__":
juguete = Juguete()
# Estado inicial: Apagado
print("Estado inicial:", juguete.get_estado())
# Presionamos el botón de encender
juguete.presionar_boton_encender()
print("Estado después de presionar_boton_encender:", juguete.get_estado())
# Presionamos el botón de jugar
juguete.presionar_boton_jugar()
print("Estado después de presionar_boton_jugar:", juguete.get_estado())
# Presionamos el botón de detener
juguete.presionar_boton_detener()
print("Estado después de presionar_boton_detener:", juguete.get_estado())
# Presionamos el botón de encender nuevamente
juguete.presionar_boton_encender()
print("Estado después de presionar_boton_encender:", juguete.get_estado())
# Presionamos el botón de jugar
juguete.presionar_boton_jugar()
print("Estado después de presionar_boton_jugar:", juguete.get_estado())
# Presionamos el botón de detener
juguete.presionar_boton_detener()
print("Estado después de presionar_boton_detener:", juguete.get_estado())
# Definir las estrategias como funciones
# Estado inicial del juguete
modo = "APAGADO"
def presionar_boton_encender():
global modo
if modo == "APAGADO":
modo = "LISTO"
print("El juguete está listo para jugar.") # Salida esperada
elif modo == "LISTO":
modo = "JUGANDO"
print("El juguete está jugando.") # Salida esperada
elif modo == "JUGANDO":
modo = "APAGADO"
print("El juguete está apagado.") # Salida esperada
def presionar_boton_jugar():
global modo
if modo == "APAGADO":
print("El juguete está apagado. Enciéndelo primero.") # Salida esperada
elif modo == "LISTO":
modo = "JUGANDO"
print("El juguete está jugando.") # Salida esperada
elif modo == "JUGANDO":
print("El juguete ya está jugando.") # Salida esperada
def presionar_boton_detener():
global modo
if modo == "APAGADO":
print("El juguete está apagado.") # Salida esperada
elif modo == "LISTO":
print("El juguete está listo, pero no está jugando.") # Salida esperada
elif modo == "JUGANDO":
modo = "LISTO"
print("El juguete ha dejado de jugar.") # Salida esperada
# Ejemplo de uso
if __name__ == "__main__":
# Estado inicial: Apagado
print("Estado inicial:", modo) # Estado inicial: APAGADO
# Presionamos el botón de encender
presionar_boton_encender()
print("Estado después de presionar_boton_encender:", modo) # Estado después de presionar_boton_encender: LISTO
# Presionamos el botón de jugar
presionar_boton_jugar()
print("Estado después de presionar_boton_jugar:", modo) # Estado después de presionar_boton_jugar: JUGANDO
# Presionamos el botón de detener
presionar_boton_detener()
print("Estado después de presionar_boton_detener:", modo) # Estado después de presionar_boton_detener: LISTO
# Presionamos el botón de encender nuevamente
presionar_boton_encender()
print("Estado después de presionar_boton_encender:", modo) # Estado después de presionar_boton_encender: JUGANDO
# Presionamos el botón de jugar
presionar_boton_jugar()
print("Estado después de presionar_boton_jugar:", modo) # Estado después de presionar_boton_jugar: JUGANDO
# Presionamos el botón de detener
presionar_boton_detener()
print("Estado después de presionar_boton_detener:", modo) # Estado después de presionar_boton_detener: LISTO
Diferencias entre Programación Estructurada y Patrón State
Programación Estructurada
- Lógica centralizada:Toda la lógica para manejar los diferentes estados está centralizada en funciones globales.
- Uso de condicionales:Depende de múltiples sentencias
if-elif-elsepara cambiar el comportamiento basado en el estado actual. - Uso de variables globales: Utiliza variables globales para mantener el estado, lo que puede complicar el mantenimiento y la comprensión del código.
- Dificultad para escalar: Añadir nuevos estados o cambiar comportamientos puede ser complicado y propenso a errores debido a la necesidad de modificar múltiples puntos en la lógica condicional.
Patrón State
- Lógica descentralizada: La lógica para cada estado está encapsulada en clases separadas, lo que hace que el código sea más modular.
- Eliminación de condicionales: Se elimina la necesidad de múltiples sentencias
if-elif-else. Cada estado sabe cómo manejar las acciones específicas. - Facilidad para escalar: Añadir nuevos estados es más sencillo y menos propenso a errores. Solo necesitas crear una nueva clase que extienda la clase base
Estadoy actualizar las transiciones entre estados.
Práctica
Para practicar, les invito a ver nuestro tutorial en video sobre el patrón de diseño State en Python, presentado por Yuscu Mejía:
Recorda si te gusta los videos, te agradeceria si le pones un comentario o un like para saber si subir mas videos! 😮
Si no sabes que comentar, comenta con: estoy avanzando rapido con el curso de Programacion python 🙂 asi te detectaremos que viniste de aca, para una estadistica si conviene o no subir videos

Ejercicio
Ahora les propongo un ejercicio para aplicar lo que hemos aprendido sobre el patrón de diseño State en Python, con la guía de Yuscu Mejía:
Ejercicio: Implementación del Patrón State
Desarrollar un sistema de control de tráfico para un cruce de calles con semáforos, utilizando el patrón de diseño State. El sistema debe manejar tres estados de semáforo: Verde, Amarillo, y Rojo. Además, debe gestionar los botones de peatones que permiten cambiar el semáforo a Rojo si está en Verde o Amarillo. El sistema debe considerar un estado de emergencia, donde todos los semáforos cambian a Rojo cuando se activa una alarma de emergencia.
Requisitos:
- Implementar los estados del semáforo (Verde, Amarillo, Rojo y Emergencia).
- Implementar un sistema que maneje los botones de los peatones y la alarma de emergencia.
- Utilizar el patrón de diseño State para encapsular el comportamiento de cada estado.
- Proveer métodos para cambiar el estado del semáforo basado en los eventos (presionar_boton_peaton, activar_alarma_emergencia)./li>
Veamos cómo Yuscu Mejía implementaría la solución a este ejercicio:
#Ejercicio Resuelto
Clase Base para el Estado
python
from abc import ABC, abstractmethod
class EstadoSemaforo(ABC):
def __init__(self, semaforo):
self.semaforo = semaforo
@abstractmethod
def presionar_boton_peaton(self):
pass
@abstractmethod
def cambiar_estado(self):
pass
@abstractmethod
def activar_alarma_emergencia(self):
pass
Clases para los Estados Específicos
python
class EstadoVerde(EstadoSemaforo):
def presionar_boton_peaton(self):
print("Botón de peatón presionado. Cambiando a Amarillo.")
self.semaforo.cambiar_estado(EstadoAmarillo(self.semaforo))
def cambiar_estado(self):
print("Cambiando de Verde a Amarillo.")
self.semaforo.cambiar_estado(EstadoAmarillo(self.semaforo))
def activar_alarma_emergencia(self):
print("Alarma de emergencia activada. Cambiando a Rojo.")
self.semaforo.cambiar_estado(EstadoEmergencia(self.semaforo))
class EstadoAmarillo(EstadoSemaforo):
def presionar_boton_peaton(self):
print("Botón de peatón presionado. Cambiando a Rojo.")
self.semaforo.cambiar_estado(EstadoRojo(self.semaforo))
def cambiar_estado(self):
print("Cambiando de Amarillo a Rojo.")
self.semaforo.cambiar_estado(EstadoRojo(self.semaforo))
def activar_alarma_emergencia(self):
print("Alarma de emergencia activada. Cambiando a Rojo.")
self.semaforo.cambiar_estado(EstadoEmergencia(self.semaforo))
class EstadoRojo(EstadoSemaforo):
def presionar_boton_peaton(self):
print("El semáforo ya está en Rojo. Espera a que cambie a Verde.")
def cambiar_estado(self):
print("Cambiando de Rojo a Verde.")
self.semaforo.cambiar_estado(EstadoVerde(self.semaforo))
def activar_alarma_emergencia(self):
print("Alarma de emergencia activada. Semáforo ya está en Rojo.")
class EstadoEmergencia(EstadoSemaforo):
def presionar_boton_peaton(self):
print("Alarma de emergencia activada. Todos los semáforos están en Rojo.")
def cambiar_estado(self):
print("Alarma de emergencia activada. Todos los semáforos están en Rojo.")
def activar_alarma_emergencia(self):
print("La alarma de emergencia ya está activada.")
Clase Semáforo
python
class Semaforo:
def __init__(self):
self.estado = EstadoRojo(self) # El semáforo empieza en Rojo
def cambiar_estado(self, estado):
self.estado = estado
def presionar_boton_peaton(self):
self.estado.presionar_boton_peaton()
def cambiar_estado(self):
self.estado.cambiar_estado()
def activar_alarma_emergencia(self):
self.estado.activar_alarma_emergencia()
def get_estado(self):
return self.estado.__class__.__name__
Clase Principal
python
if __name__ == "__main__":
semaforo = Semaforo()
# Estado inicial: Rojo
print("Estado inicial:", semaforo.get_estado()) # Estado inicial: EstadoRojo
# Cambiar estado del semáforo
semaforo.cambiar_estado()
print("Estado después de cambiar_estado:", semaforo.get_estado()) # Estado después de cambiar_estado: EstadoVerde
# Presionar el botón de peatón
semaforo.presionar_boton_peaton()
print("Estado después de presionar_boton_peaton:", semaforo.get_estado()) # Estado después de presionar_boton_peaton: EstadoAmarillo
# Cambiar estado del semáforo
semaforo.cambiar_estado()
print("Estado después de cambiar_estado:", semaforo.get_estado()) # Estado después de cambiar_estado: EstadoRojo
# Activar alarma de emergencia
semaforo.activar_alarma_emergencia()
print("Estado después de activar_alarma_emergencia:", semaforo.get_estado()) # Estado después de activar_alarma_emergencia: EstadoEmergencia
# Intentar cambiar estado del semáforo durante emergencia
semaforo.cambiar_estado()
print("Estado después de intentar cambiar_estado durante emergencia:", semaforo.get_estado()) # Estado después de intentar cambiar_estado durante emergencia: EstadoEmergencia
# Intentar presionar botón de peatón durante emergencia
semaforo.presionar_boton_peaton()
print("Estado después de intentar presionar_boton_peaton durante emergencia:", semaforo.get_estado()) # Estado después de in

