Skip to content
SP StackPractices
intermediate

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.

Temas: design

Patrón State

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

VarianteDescripciónCaso de Uso
Máquina de Estados SimpleLos estados manejan transicionesPequeño número de estados
Máquina de Estados JerárquicaLos estados pueden tener subestadosComportamiento anidado complejo
Tabla de EstadosLas transiciones se almacenan en tabla de búsquedaMuchos estados con transiciones predecibles
Autómata de PilaHistorial de estado basado en pilaTransiciones de estado deshacer/reversibles

Buenas Prácticas

  • 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

Preguntas Frecuentes

P: ¿Cuál es la diferencia entre State y Strategy? R: Ambos encapsulan comportamiento en objetos intercambiables. State es sobre cambiar comportamiento basado en transiciones de estado internas. Strategy es sobre seleccionar un algoritmo desde afuera. La intención difiere: State gestiona ciclo de vida, Strategy proporciona opciones.

P: ¿Los estados deben conocerse entre sí? R: En la implementación clásica, los estados disparan transiciones a otros estados. Una alternativa es dejar que el contexto o una tabla de transiciones gestione las transiciones, manteniendo los estados desacoplados.

P: ¿Puedo usar enums en lugar de clases para máquinas de estado simples? R: Sí. Para casos simples, un enum con un switch es suficiente. Usa el Patrón State completo cuando los estados tengan comportamiento complejo o esperes agregados frecuentes.