Saltar al contenido

State

Un patrón de comportamiento: el objeto delega en su estado actual qué puede hacer, y ese estado puede cambiar a lo largo de su vida.

8 min. de lectura

State es un patrón de comportamiento que permite que un objeto cambie su comportamiento cuando cambia su estado interno, dando la impresión de que cambió de clase.

El problema

Un pedido de AndesShop pasa por los estados PENDING, PAID, SHIPPED, DELIVERED y CANCELLED, y lo que se puede hacer con él depende de en cuál esté: se puede cancelar si está PENDING o PAID, pero no si ya está SHIPPED; se puede marcar como enviado solo si está PAID. Modelar esto con un campo status y un condicional gigante en cada método (cancel(), markAsShipped(), etc.) que revisa en qué estado está, funciona al principio — pero cada estado nuevo obliga a revisar y actualizar todos esos condicionales, en todos los métodos, y es fácil olvidarse de uno.

La solución

State extrae el comportamiento específico de cada estado a su propia clase, y todas implementan la misma interfaz. El pedido delega en su estado actual las decisiones sobre qué hacer, y es el propio objeto de estado quien decide, además, a qué estado pasar después.

Estructura de State: cada estado implementa la misma interfaz y decide a qué estado transiciona. Las flechas punteadas son las transiciones válidas.

Ejemplo en Java

interface OrderState {
    void cancel(Order order);
    void markAsShipped(Order order);
}

class PendingState implements OrderState {
    public void cancel(Order order) {
        order.setState(new CancelledState());
    }
    public void markAsShipped(Order order) {
        throw new IllegalStateException("Cannot ship an unpaid order");
    }
}

class PaidState implements OrderState {
    public void cancel(Order order) {
        order.setState(new CancelledState());
    }
    public void markAsShipped(Order order) {
        order.setState(new ShippedState());
    }
}

class ShippedState implements OrderState {
    public void cancel(Order order) {
        throw new IllegalStateException("Cannot cancel a shipped order");
    }
    public void markAsShipped(Order order) {
        throw new IllegalStateException("Already shipped");
    }
}

class CancelledState implements OrderState {
    public void cancel(Order order) {
        throw new IllegalStateException("Already cancelled");
    }
    public void markAsShipped(Order order) {
        throw new IllegalStateException("Cannot ship a cancelled order");
    }
}

class Order {
    private OrderState state = new PendingState();

    // Package-private a propósito: la transición la disparan los propios estados,
    // que viven en este paquete. Si fuera pública, cualquier cliente podría poner
    // un pedido en SHIPPED sin pasar por PaidState, rompiendo la invariante que
    // este patrón busca preservar.
    void setState(OrderState state) { this.state = state; }

    public void cancel() { state.cancel(this); }
    public void markAsShipped() { state.markAsShipped(this); }
}
Order order = new Order();
order.markAsShipped(); // lanza excepción: todavía está PENDING

Cuándo usarlo

  • Cuando el comportamiento de un objeto cambia según su estado interno, y ese comportamiento involucra varios métodos que necesitan chequear el mismo campo de estado.
  • Cuando el número de estados posibles y las transiciones entre ellos son lo bastante complejos como para que un único condicional se vuelva difícil de mantener.

Cuándo evitarlo

Con dos o tres estados de comportamiento casi idéntico entre sí, un enum con algún condicional simple puede ser más directo que crear una clase por estado.

Ventajas y desventajas

VentajasDesventajas
Elimina condicionales grandes repartidos en varios métodosPuede ser excesivo si hay pocos estados y poca diferencia de comportamiento entre ellos
Cada estado queda encapsulado en su propia clase, fácil de entender y modificar por separadoAgrega una clase nueva por cada estado
Las transiciones quedan explícitas, en vez de escondidas en asignaciones sueltas de un campo

Relación con otros patrones

  • Es la forma orientada a objetos de proteger las transiciones válidas de un aggregate. El tema de arquitectura llama invariante justamente a lo que acá impide que un pedido enviado se cancele.
  • Se implementa de forma muy similar a Strategy — ambos delegan comportamiento a un objeto intercambiable — pero con intención distinta: Strategy lo elige el código cliente; State lo cambian los propios objetos de estado entre sí, según transiciones internas.

Te sirvió, compartilo

// ¿te sirvió?
// compartilo