Patrón State
Permite que un objeto altere su comportamiento cuando cambia su estado interno. Un patrón de comportamiento para máquinas de estados finitos.
Visión General
El Patrón State es un patrón de diseño de comportamiento que permite que un objeto altere su comportamiento cuando cambia su estado interno. En lugar de grandes sentencias switch o condicionales dispersas por todo el código, cada estado se encapsula en su propia clase con comportamiento específico.
Cuándo Usarlo
Usa el Patrón State cuando:
- El comportamiento de un objeto depende de su estado y debe cambiar en tiempo de ejecución
- Tienes sentencias condicionales grandes que cambian comportamiento según el estado
- Los estados tienen transiciones complejas con acciones de entrada/salida
- Quieres agregar nuevos estados sin modificar clases de estado existentes
Solución
Python
from abc import ABC, abstractmethod
class OrderState(ABC):
@abstractmethod
def pay(self, order):
pass
@abstractmethod
def ship(self, order):
pass
@abstractmethod
def cancel(self, order):
pass
class PendingState(OrderState):
def pay(self, order):
print("Procesando pago...")
order.transition_to(PaidState())
def ship(self, order):
print("No se puede enviar: pago pendiente")
def cancel(self, order):
print("Pedido cancelado")
order.transition_to(CancelledState())
class PaidState(OrderState):
def pay(self, order):
print("Ya pagado")
def ship(self, order):
print("Enviando pedido...")
order.transition_to(ShippedState())
def cancel(self, order):
print("Emitiendo reembolso...")
order.transition_to(CancelledState())
class ShippedState(OrderState):
def pay(self, order):
print("Ya pagado")
def ship(self, order):
print("Ya enviado")
def cancel(self, order):
print("No se puede cancelar: ya enviado")
class CancelledState(OrderState):
def pay(self, order):
print("No se puede pagar: pedido cancelado")
def ship(self, order):
print("No se puede enviar: pedido cancelado")
def cancel(self, order):
print("Ya cancelado")
class Order:
def __init__(self):
self._state = PendingState()
def transition_to(self, state):
self._state = state
def pay(self):
self._state.pay(self)
def ship(self):
self._state.ship(self)
def cancel(self):
self._state.cancel(self)
# Uso
order = Order()
order.pay()
order.ship()
order.cancel() # No se puede cancelar: ya enviado
JavaScript
class Order {
constructor() { this.state = new PendingState(this); }
transitionTo(state) { this.state = state; }
pay() { this.state.pay(); }
ship() { this.state.ship(); }
cancel() { this.state.cancel(); }
}
class PendingState {
constructor(order) { this.order = order; }
pay() {
console.log("Procesando pago...");
this.order.transitionTo(new PaidState(this.order));
}
ship() { console.log("No se puede enviar: pago pendiente"); }
cancel() {
console.log("Pedido cancelado");
this.order.transitionTo(new CancelledState(this.order));
}
}
class PaidState {
constructor(order) { this.order = order; }
pay() { console.log("Ya pagado"); }
ship() {
console.log("Enviando pedido...");
this.order.transitionTo(new ShippedState(this.order));
}
cancel() {
console.log("Emitiendo reembolso...");
this.order.transitionTo(new CancelledState(this.order));
}
}
class ShippedState {
constructor(order) { this.order = order; }
pay() { console.log("Ya pagado"); }
ship() { console.log("Ya enviado"); }
cancel() { console.log("No se puede cancelar: ya enviado"); }
}
class CancelledState {
constructor(order) { this.order = order; }
pay() { console.log("No se puede pagar: pedido cancelado"); }
ship() { console.log("No se puede enviar: pedido cancelado"); }
cancel() { console.log("Ya cancelado"); }
}
const order = new Order();
order.pay();
order.ship();
order.cancel(); // No se puede cancelar: ya enviado
Java
interface OrderState {
void pay(Order order);
void ship(Order order);
void cancel(Order order);
}
class PendingState implements OrderState {
public void pay(Order order) {
System.out.println("Procesando pago...");
order.transitionTo(new PaidState());
}
public void ship(Order order) {
System.out.println("No se puede enviar: pago pendiente");
}
public void cancel(Order order) {
System.out.println("Pedido cancelado");
order.transitionTo(new CancelledState());
}
}
class PaidState implements OrderState {
public void pay(Order order) {
System.out.println("Ya pagado");
}
public void ship(Order order) {
System.out.println("Enviando pedido...");
order.transitionTo(new ShippedState());
}
public void cancel(Order order) {
System.out.println("Emitiendo reembolso...");
order.transitionTo(new CancelledState());
}
}
class ShippedState implements OrderState {
public void pay(Order order) {
System.out.println("Ya pagado");
}
public void ship(Order order) {
System.out.println("Ya enviado");
}
public void cancel(Order order) {
System.out.println("No se puede cancelar: ya enviado");
}
}
class CancelledState implements OrderState {
public void pay(Order order) {
System.out.println("No se puede pagar: pedido cancelado");
}
public void ship(Order order) {
System.out.println("No se puede enviar: pedido cancelado");
}
public void cancel(Order order) {
System.out.println("Ya cancelado");
}
}
class Order {
private OrderState state = new PendingState();
public void transitionTo(OrderState state) {
this.state = state;
}
public void pay() { state.pay(this); }
public void ship() { state.ship(this); }
public void cancel() { state.cancel(this); }
}
// Uso
Order order = new Order();
order.pay();
order.ship();
order.cancel(); // No se puede cancelar: ya enviado
Explicación
El Patrón State involucra dos roles:
- Contexto (
Order): Mantiene una referencia al estado actual y delega comportamiento a él - Interfaz de Estado (
OrderState): Define el contrato para comportamiento específico de estado - Estados Concretos (
PendingState,PaidState, etc.): Implementan comportamiento específico para cada estado y manejan transiciones
Variantes
| Variante | Descripción | Caso de Uso |
|---|---|---|
| Máquina de Estados Simple | Los estados manejan transiciones | Pequeño número de estados |
| Máquina de Estados Jerárquica | Los estados pueden tener subestados | Comportamiento anidado complejo |
| Tabla de Estados | Las transiciones se almacenan en tabla de búsqueda | Muchos estados con transiciones predecibles |
| Autómata de Pila | Historial de estado basado en pila | Transiciones de estado deshacer/reversibles. Consulta Memento |
Lo que funciona
- Delega todo el comportamiento dependiente del estado a objetos de estado, manteniendo el contexto delgado
- Haz los objetos de estado inmutables o recréalos en transición para evitar bugs de estado compartido
- Usa enums para estados bien definidos en lenguajes que los soportan (Java, TypeScript)
- Agrega hooks de entrada/salida cuando las transiciones necesiten efectos secundarios (logging, analytics)
- Considera una tabla de transiciones cuando tengas muchos estados y las transiciones se vuelvan difíciles de gestionar
Errores Comunes
- Duplicar lógica de transición entre múltiples clases de estado en lugar de centralizarla
- Permitir que el contexto modifique el estado directamente en lugar de delegar al estado actual
- Olvidar manejar transiciones inválidas con gracia, llevando a errores en tiempo de ejecución
- Crear transiciones circulares de estado que puedan causar bucles infinitos
- Mezclar lógica de estado con lógica de negocio, haciendo el patrón difícil de probar
Puntos Clave
- Aplica patrón state cuando necesites una solución práctica para tu caso de uso.
- Monitorea el rendimiento después de implementar; mide latencia, errores y uso de recursos antes y después.
- Revisa la sección de Troubleshooting ante errores comunes; la mayoría tienen causa raíz documentada con solución.
- Mantén dependencias actualizadas y ejecuta tests en CI para prevenir regresiones en producción.
Troubleshooting
- Pattern does not fit the problem: re-evaluate the forces (performance, scalability, team size, coupling). A pattern is only appropriate when its trade-offs match your constraints.
- Too many abstractions: if adding a pattern increases complexity without a clear benefit, simplify. Not every module needs a factory, decorator, or strategy.
- Tight coupling after refactoring: check that interfaces are stable and dependencies point inward.
- Tests break when the design changes: favor stable contracts over internal structure.
- Performance regression from indirection: measure before and after. Layers, decorators, and adapters can add latency; cache or inline hot paths if needed.
Errores Comunes en Producción
- Aplicar el patrón donde no se necesita abstracción, agregando complejidad accidental.
- Dejar que el patrón se filtre en módulos no relacionados y confundir los límites de responsabilidad.
- Sobre-ingeniería en la primera implementación en lugar de comenzar simple y medir el dolor.
- Saltar los tests de contrato, de modo que las refactorizaciones rompan consumidores en silencio.
- Ignorar modos de fallo que el patrón no cubre.
- Usar el patrón como opción por defecto en lugar de elegir la herramienta adecuada para la escala actual.
- Olvidar documentar cuándo dejar de usar el patrón y qué lo reemplaza.
- Carecer de observabilidad sobre rendimiento y propagación de errores del patrón.
Preguntas frecuentes
¿Es este patrón adecuado para proyectos pequeños?
Para proyectos pequeños con pocos componentes, este patrón puede añadir complejidad innecesaria. Empieza simple e introduce el patrón cuando sientas el problema que resuelve.
¿Cómo se compara este patrón con alternativas?
Cada patrón hace diferentes trade-offs. Revisa la tabla de variantes arriba y considera tus restricciones específicas: tamaño del equipo, requisitos de rendimiento y planes de escalado.
¿Puedo aplicar este patrón parcialmente?
Sí. Muchos equipos adoptan patrones incrementalmente. Empieza con la idea central y añade sofisticación según sea necesario. El patrón es una guía, no un blueprint estricto.
Recursos Relacionados
Patrón Strategy
Define una familia de algoritmos, encapsula cada uno y los hace intercambiables. Patrón de diseño conductual para selección flexible de comportamiento.
PatternPatrón Command
Encapsula una petición como un objeto, permitiendo parametrizar clientes con colas, logs y operaciones deshacibles. Patrón de diseño conductual.
PatternPatrón Observer
Define un mecanismo de suscripción para notificar a múltiples objetos sobre eventos. Patrón de diseño conductual para comunicación basada en eventos.
PatternPatrón Mediator
Define un objeto que encapsula cómo interactúa un conjunto de objetos. Un patrón de comportamiento para reducir dependencias caóticas.
PatternPatrón Memento
Captura y restaura el estado interno de un objeto sin violar el encapsulamiento. Un patrón de comportamiento para deshacer/rehacer.