Saltar al contenido
DevPedia

Patrones de comportamientoGuía 24 de 28

State

Cómo organizar el comportamiento de un objeto según su estado y hacer explícitas las transiciones permitidas.

Actualizado 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 hay tantos estados y transiciones que un único condicional, repartido en varios métodos, se vuelve difícil de mantener.

Cuándo evitarlo

Con dos o tres estados que se comportan casi igual, un enum con un condicional simple suele ser más directo que 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 vive en su propia clase: el comportamiento de SHIPPED se lee y se cambia sin tocar el de PENDINGAgrega 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 agregado. 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.

Compartir esta guía

Buscá por concepto, patrón o práctica.