Problema

Cambiar el comportamiento de un objeto según su estado interno, evitando condicionales complejas.

Propósito

Cada estado tiene su propia clase con su comportamiento específico. El objeto delega las operaciones al estado actual, eliminando if/switch complejos.

Concepto clave

Estado como clase: En lugar de if (estado == PENDIENTE), tienes una clase EstadoPendiente que sabe qué hacer. El objeto cambia de clase de estado automáticamente.

Casos de uso comunes

  • State machines (máquinas de estado)
  • Workflows y procesos de negocio
  • Estados de conexión (conectado, desconectado, reconectando)
  • Estados de pedidos (pendiente, confirmado, enviado, entregado)
  • Estados de documentos (borrador, revisión, aprobado, publicado)
  • Estados de juegos (menú, jugando, pausado, game over)

¿Quién es quién en State?

Actor Lo que realmente es Ejemplo Analogía
Context Objeto que cambia de comportamiento Order - mantiene referencia al estado actual Semáforo (el objeto físico)
State Interfaz que define operaciones OrderState - define qué puede hacer cada estado “Comportamiento de color” (interfaz)
ConcreteState Estados específicos con lógica PendingState, ConfirmedState Rojo, Amarillo, Verde (saben qué hacer)

Diagrama

classDiagram
    namespace StatePattern {
        class Context {
            -state: State
            +setState(state)
            +request1()
            +request2()
        }
        
        class State {
            <<interface>>
            +handle1(context)
            +handle2(context)
        }
        
        class ConcreteStateA {
            +handle1(context)
            +handle2(context)
        }
        
        class ConcreteStateB {
            +handle1(context)
            +handle2(context)
        }
        
        class ConcreteStateC {
            +handle1(context)
            +handle2(context)
        }
    }
    
    Context --> State
    State <|.. ConcreteStateA
    State <|.. ConcreteStateB
    State <|.. ConcreteStateC

Ejemplo práctico

classDiagram
    namespace OrderExample {
        class Order {
            -state: OrderState
            -orderData: OrderData
            +setState(state)
            +confirm()
            +ship()
            +deliver()
            +cancel()
        }
        
        class OrderState {
            <<interface>>
            +confirm(order)
            +ship(order)
            +deliver(order)
            +cancel(order)
        }
        
        class PendingState {
            +confirm(order)
            +ship(order)
            +deliver(order)
            +cancel(order)
        }
        
        class ConfirmedState {
            +confirm(order)
            +ship(order)
            +deliver(order)
            +cancel(order)
        }
        
        class ShippedState {
            +confirm(order)
            +ship(order)
            +deliver(order)
            +cancel(order)
        }
        
        class DeliveredState {
            +confirm(order)
            +ship(order)
            +deliver(order)
            +cancel(order)
        }
        
        class CancelledState {
            +confirm(order)
            +ship(order)
            +deliver(order)
            +cancel(order)
        }
    }
    
    Order --> OrderState
    OrderState <|.. PendingState
    OrderState <|.. ConfirmedState
    OrderState <|.. ShippedState
    OrderState <|.. DeliveredState
    OrderState <|.. CancelledState

Transiciones de estado

stateDiagram-v2
    [*] --> Pending
    Pending --> Confirmed : confirm()
    Pending --> Cancelled : cancel()
    
    Confirmed --> Shipped : ship()
    Confirmed --> Cancelled : cancel()
    
    Shipped --> Delivered : deliver()
    Shipped --> Cancelled : cancel()
    
    Delivered --> [*]
    Cancelled --> [*]
    
    note right of Pending : Puede confirmar o cancelar
    note right of Confirmed : Puede enviar o cancelar
    note right of Shipped : Puede entregar o cancelar
    note right of Delivered : Estado final
    note right of Cancelled : Estado final

¿Quién maneja las transiciones de estado?

Hay 3 enfoques diferentes:

Enfoque 1: Estado cambia el Context (más común)

// El estado se cambia a sí mismo
public class PendingState implements OrderState {
    public void confirm(Order order) {
        // Validar transición
        if (canConfirm()) {
            // El estado cambia el contexto
            order.setState(new ConfirmedState());
            // Ejecutar acciones del cambio
            order.sendConfirmationEmail();
        }
    }
}

Enfoque 2: Context maneja las transiciones

// El contexto decide cuándo cambiar
public class Order {
    public void confirm() {
        // El estado solo valida y ejecuta
        boolean canConfirm = state.canConfirm();
        if (canConfirm) {
            state.performConfirmActions(this);
            // El contexto decide el siguiente estado
            this.state = new ConfirmedState();
        }
    }
}

Enfoque 3: Estados retornan el siguiente estado

// El estado retorna el siguiente estado
public class PendingState implements OrderState {
    public OrderState confirm(Order order) {
        // Ejecutar acciones
        order.sendConfirmationEmail();
        // Retornar siguiente estado
        return new ConfirmedState();
    }
}

public class Order {
    public void confirm() {
        this.state = state.confirm(this);
    }
}

Flujo recomendado (Enfoque 1)

sequenceDiagram
    participant Client
    participant Order
    participant PendingState
    participant ConfirmedState
    
    Client->>Order: confirm()
    Order->>PendingState: confirm(this)
    PendingState->>PendingState: validate transition
    
    alt Transition is valid
        PendingState->>Order: setState(new ConfirmedState())
        Order->>Order: state = ConfirmedState
        PendingState->>Order: performConfirmActions()
    else Transition invalid
        PendingState->>Order: throw InvalidTransitionException
    end

Ventajas

  • Eliminación de condicionales: No más if/switch complejos
  • Extensibilidad: Fácil agregar nuevos estados
  • Encapsulación: Cada estado encapsula su comportamiento
  • Transiciones explícitas: Las transiciones son claras y controladas

Desventajas

  • Complejidad: Introduce muchas clases pequeñas
  • Overhead: Puede ser excesivo para state machines simples
  • Distribución: La lógica se distribuye entre múltiples clases
  • Dependencias: Estados pueden necesitar conocer otros estados

Cuándo usar

  • Tienes objetos con comportamiento que cambia según su estado
  • Muchas condicionales basadas en estado
  • Las transiciones de estado son complejas
  • Quieres hacer explícitas las transiciones de estado

Cuándo NO usar

  • Pocos estados simples
  • Las transiciones son triviales
  • El comportamiento no cambia significativamente entre estados
  • Prefieres simplicidad sobre flexibilidad

¿Cuál enfoque usar?

Enfoque Ventajas Desventajas Cuándo usar
Estado cambia Context Simple, estados autónomos Acoplamiento, estados conocen otros estados State machines complejas
Context maneja cambios Bajo acoplamiento, control centralizado Context más complejo Transiciones simples
Estado retorna siguiente Funcional, testeable Menos intuitivo Cuando prefieres inmutabilidad

Recomendación: Usa Enfoque 1 (estado cambia context) para la mayoría de casos. Es el más común y natural.

Diferencias con otros patrones

  • vs Strategy: State cambia automáticamente según estado interno, Strategy se cambia externamente por el cliente
  • vs Command: State encapsula comportamiento basado en estado, Command encapsula operaciones como objetos