Patrón de Estado en Python
La idea central del patrón de estado es encapsular el estado de un objeto en clases independientes y delegar el comportamiento del objeto al objeto de estado actual. Cuando el estado del objeto cambia, cambia a un objeto de estado diferente, alterando así su comportamiento.
¿Por qué necesitamos el patrón de estado?
Sin usar el patrón de estado, normalmente usamos una gran cantidad deif-elseoswitch-casesentencias para manejar comportamientos en diferentes estados. Este enfoque tiene varios problemas:
- Código inflado: A medida que aumenta el número de estados, las sentencias condicionales se vuelven cada vez más complejas
- Difícil de mantener: Modificar el comportamiento de un estado puede afectar a otros estados
- Viola el principio abierto/cerrado: Agregar un nuevo estado requiere modificar el código existente
El patrón de estado resuelve estos problemas encapsulando cada estado en una clase independiente.
Estructura del patrón de estado
Roles principales
El patrón de estado incluye tres roles principales:
- Context (Contexto): Mantiene una instancia de un objeto de estado concreto, que define el estado actual
- State (Interfaz de estado): Define una interfaz para encapsular el comportamiento asociado con un estado específico del Contexto
- ConcreteState (Estado concreto): Implementa la interfaz State. Cada clase de estado concreto implementa un comportamiento relacionado con el estado del Contexto
Diagrama de clases UML

Implementación del patrón de estado
Pasos básicos de implementación
Entendamos la implementación del patrón de estado con un ejemplo sencillo. Supongamos que tenemos un sistema de ascensor, el ascensor tiene diferentes estados: puerta abierta, puerta cerrada, en movimiento y detenido.
Paso 1: Definir la interfaz de estado
Ejemplo
class ElevatorState(ABC):
"""Interfaz de estado del ascensor"""
@abstractmethod
def open_doors(self):
"""Operación de abrir la puerta"""
pass
@abstractmethod
def close_doors(self):
"""Operación de cerrar la puerta"""
pass
@abstractmethod
def move(self):
"""Operación de movimiento"""
pass
@abstractmethod
def stop(self):
"""Operación de detener"""
pass
Paso 2: Implementar las clases de estado concreto
Ejemplo
"""Estado de puerta abierta"""
def open_doors(self):
print("La puerta ya está abierta")
return self
def close_doors(self):
print("Cerrando la puerta...")
return DoorClosedState()
def move(self):
print("Error: la puerta sigue abierta, no se puede mover")
return self
def stop(self):
print("El ascensor ya está detenido")
return self
class DoorClosedState(ElevatorState):
"""Estado de puerta cerrada"""
def open_doors(self):
print("Abriendo la puerta...")
return DoorOpenState()
def close_doors(self):
print("La puerta ya está cerrada")
return self
def move(self):
print("El ascensor comienza a moverse...")
return MovingState()
def stop(self):
print("El ascensor ya está detenido")
return self
class MovingState(ElevatorState):
"""Estado de movimiento"""
def open_doors(self):
print("Error: el ascensor está en movimiento, no se puede abrir la puerta")
return self
def close_doors(self):
print("La puerta ya está cerrada")
return self
def move(self):
print("El ascensor se está moviendo")
return self
def stop(self):
print("El ascensor se está deteniendo...")
return DoorClosedState()
Paso 3: Crear la clase de contexto
Ejemplo
"""Clase de contexto del ascensor"""
def __init__(self):
# El estado inicial es el estado de puerta cerrada
self._state = DoorClosedState()
@property
def state(self):
"""Obtener el estado actual"""
return self._state
def set_state(self, state):
"""Establecer un nuevo estado"""
self._state = state
print(f"El estado ha cambiado a: {state.__class__.__name__}")
def open_doors(self):
"""Operación de abrir la puerta"""
print("Ejecutando operación de abrir la puerta...")
self.set_state(self._state.open_doors())
def close_doors(self):
"""Operación de cerrar la puerta"""
print("Ejecutando operación de cerrar la puerta...")
self.set_state(self._state.close_doors())
def move(self):
"""Operación de movimiento"""
print("Ejecutando operación de movimiento...")
self.set_state(self._state.move())
def stop(self):
"""Operación de detener"""
print("Ejecutando operación de detener...")
self.set_state(self._state.stop())
Paso 4: Usar el patrón de estado
Ejemplo
elevator = Elevator()
# Probar la transición de estados
print("=== Prueba de transición de estados del ascensor ===")
elevator.open_doors() # Cambiar del estado de puerta cerrada al estado de puerta abierta
elevator.close_doors() # Cambiar del estado de puerta abierta al estado de puerta cerrada
elevator.move() # Cambiar del estado de puerta cerrada al estado de movimiento
elevator.stop() # Cambiar del estado de movimiento al estado de puerta cerrada
print("\n"=== Prueba de operación ilegal ===")
elevator.open_doors() # Abrir la puerta normalmente
elevator.move() # Intentar moverse en el estado de puerta abierta (debería dar error)
Resultado de ejecución
=== 电梯状态转换测试 === 执行开门操作... 正在开门... 状态已切换为: DoorOpenState 执行关门操作... 正在关门... 状态已切换为: DoorClosedState 执行移动操作... 电梯开始移动... 状态已切换为: MovingState 执行停止操作... 电梯停止中... 状态已切换为: DoorClosedState === 测试非法操作 === 执行开门操作... 正在开门... 状态已切换为: DoorOpenState 执行移动操作... 错误:门还开着,不能移动 状态已切换为: DoorOpenState
Aplicación avanzada del patrón de estado
Patrón de estado con estado compartido
En algunos casos, múltiples contextos pueden necesitar compartir el mismo objeto de estado. Podemos lograr el estado compartido mediante el patrón singleton.
Ejemplo
"""Metaclase singleton"""
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
class SharedDoorOpenState(ElevatorState, metaclass=Singleton):
"""Estado de puerta abierta compartido (singleton)"""
def open_doors(self):
print("La puerta ya está abierta")
return self
def close_doors(self):
print("Cerrando la puerta...")
return SharedDoorClosedState()
def move(self):
print("Error: la puerta sigue abierta, no se puede mover")
return self
def stop(self):
print("El ascensor ya está detenido")
return self
class SharedDoorClosedState(ElevatorState, metaclass=Singleton):
"""Estado de puerta cerrada compartido (singleton)"""
def open_doors(self):
print("Abriendo la puerta...")
return SharedDoorOpenState()
def close_doors(self):
print("La puerta ya está cerrada")
return self
def move(self):
print("El ascensor comienza a moverse...")
return SharedMovingState()
def stop(self):
print("El ascensor ya está detenido")
return self
class SharedMovingState(ElevatorState, metaclass=Singleton):
"""Estado de movimiento compartido (singleton)"""
def open_doors(self):
print("Error: el ascensor está en movimiento, no se puede abrir la puerta")
return self
def close_doors(self):
print("La puerta ya está cerrada")
return self
def move(self):
print("El ascensor se está moviendo")
return self
def stop(self):
print("El ascensor se está deteniendo...")
return SharedDoorClosedState()
Patrón de estado y máquina de estados
El patrón de estado se utiliza a menudo junto con máquinas de estado. A continuación se muestra un ejemplo más complejo de una máquina de estados:
Ejemplo
"""Interfaz de estado de la máquina expendedora"""
@abstractmethod
def insert_coin(self, machine, amount):
"""Insertar moneda"""
pass
@abstractmethod
def select_product(self, machine, product):
"""Seleccionar producto"""
pass
@abstractmethod
def dispense(self, machine):
"""Despachar producto"""
pass
@abstractmethod
def refund(self, machine):
"""Reembolsar"""
pass
class WaitingState(VendingMachineState):
"""Estado de espera de moneda"""
def insert_coin(self, machine, amount):
print(f"Inserte {amount} yuan")
machine.balance += amount
return HasMoneyState()
def select_product(self, machine, product):
print("Por favor, inserte una moneda primero")
return self
def dispense(self, machine):
print("Por favor, inserte una moneda y seleccione un producto primero")
return self
def refund(self, machine):
print("No hay monto que reembolsar")
return self
class HasMoneyState(VendingMachineState):
"""Estado de moneda insertada"""
def insert_coin(self, machine, amount):
print(f"Inserte {amount} yuan adicionales")
machine.balance += amount
return self
def select_product(self, machine, product):
if product.price <= machine.balance:
print(f"Producto seleccionado: {product.name}")
machine.selected_product = product
return ProductSelectedState()
else:
print("Saldo insuficiente, por favor inserte más monedas o seleccione un producto más barato")
return self
def dispense(self, machine):
print("Por favor, seleccione un producto primero")
return self
def refund(self, machine):
print(f"Reembolso de {machine.balance} yuan")
machine.balance = 0
return WaitingState()
class ProductSelectedState(VendingMachineState):
"""Estado de producto seleccionado"""
def insert_coin(self, machine, amount):
print("Producto ya seleccionado, no se pueden insertar más monedas")
return self
def select_product(self, machine, product):
print("Producto ya seleccionado, por favor complete la transacción actual")
return self
def dispense(self, machine):
product = machine.selected_product
change = machine.balance - product.price
print(f"Despachando: {product.name}")
if change > 0:
print(f"Cambio: {change} yuan")
machine.balance = 0
machine.selected_product = None
return WaitingState()
def refund(self, machine):
print(f"Reembolso de {machine.balance} yuan")
machine.balance = 0
machine.selected_product = None
return WaitingState()
class Product:
"""Clase de producto"""
def __init__(self, name, price):
self.name = name
self.price = price
class VendingMachine:
"""Máquina expendedora"""
def __init__(self):
self._state = WaitingState()
self.balance = 0
self.selected_product = None
def set_state(self, state):
self._state = state
def insert_coin(self, amount):
print(f"Operación: insertar {amount} yuan")
self.set_state(self._state.insert_coin(self, amount))
def select_product(self, product):
print(f"Operación: seleccionar producto {product.name}")
self.set_state(self._state.select_product(self, product))
def dispense(self):
print("Operación: solicitar despacho")
self.set_state(self._state.dispense(self))
def refund(self):
print("Operación: solicitar reembolso")
self.set_state(self._state.refund(self))
Ventajas y desventajas del patrón de estado
Ventajas
- Principio de responsabilidad única: Poner el código relacionado con un estado específico en clases independientes
- Principio abierto/cerrado: Introducir nuevos estados sin modificar las clases de estado existentes ni el contexto
- Elimina sentencias condicionales: Elimina grandes sentencias de ramas condicionales mediante llamadas polimórficas
- Transiciones de estado claras: Hace que las transiciones de estado sean más explícitas, reduciendo los errores debidos a transiciones de estado
Desventajas
- Posible sobrediseño: Si el número de estados es pequeño o estos cambian raramente, usar el patrón de estado puede ser excesivamente complejo
- Aumento en el número de clases de estado: Cada estado necesita una clase correspondiente, lo que puede provocar un aumento en el número de clases
- Contexto y acoplamiento de estadoEl contexto necesita conocer todas las clases de estado concretas para realizar las transiciones de estado
Ejercicios prácticos
Ejercicio 1: Mejorar el sistema de ascensor
Intenta añadir las siguientes funcionalidades al sistema de ascensor:
- Añadir el estado "En mantenimiento", en el que todas las operaciones están prohibidas
- Añadir la función de selección de planta, solo se puede seleccionar planta en el estado de parada
- Implementar la transición automática de estado al llegar a una planta
Ejercicio 2: Implementar el sistema de semáforo
Utiliza el patrón de estado para implementar un sistema de semáforo con tres estados: luz roja, luz verde y luz amarilla. Las reglas de transición de estado son las siguientes:
- Luz roja → Luz verde (después de 30 segundos)
- Luz verde → Luz amarilla (después de 25 segundos)
- Luz amarilla → Luz roja (después de 5 segundos)
Ejercicio 3: Estado del personaje de juego
Diseña un sistema de estados para un personaje de juego, que incluya los siguientes estados:
- Estado normal: puede moverse y atacar
- Estado envenenado: pierde vida continuamente y la velocidad de movimiento se reduce a la mitad
- Estado aturdido: no puede moverse ni atacar
- Estado invencible: no recibe daño
Resumen
El patrón de estado es un patrón de diseño potente, especialmente adecuado para manejar escenarios en los que el comportamiento de un objeto depende de su estado, con un número elevado de estados y transiciones complejas. Al encapsular cada estado en una clase independiente, el patrón de estado hace que el código sea más claro, fácil de mantener y de extender.
Puntos clave:
- El patrón de estado elimina las comprobaciones condicionales al encapsular los estados como objetos
- El contexto delega los comportamientos relacionados con el estado en el objeto de estado actual
- Las transiciones de estado pueden ser provocadas por el contexto o por el propio objeto de estado
- El uso adecuado del patrón de estado puede mejorar en gran medida la mantenibilidad del código