Skip to content
DevPedia

Behavioral patternsGuide 24 of 28

State

How to organize an object's behavior by its state and make the allowed transitions explicit.

Updated 8 min read

State is a behavioral pattern that lets an object change its behavior when its internal state changes, giving the impression that it changed class.

The problem

An AndesShop order moves through the states PENDING, PAID, SHIPPED, DELIVERED, and CANCELLED, and what you can do with it depends on which one it’s in: it can be cancelled while PENDING or PAID, but not once it’s SHIPPED; it can be marked as shipped only if it’s PAID. Modeling this with a status field and a giant conditional in every method (cancel(), markAsShipped(), and so on) that checks which state it’s in works at first — but every new state means reviewing and updating all those conditionals, in every method, and it’s easy to miss one.

The solution

State extracts each state’s specific behavior into its own class, and they all implement the same interface. The order delegates decisions about what to do to its current state, and the state object itself also decides which state to move to next.

The structure of State: each state implements the same interface and decides which state it transitions to. The dashed arrows are the valid transitions.

Example in 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 on purpose: the transition is triggered by the states
    // themselves, which live in this package. If it were public, any client could
    // put an order into SHIPPED without going through PaidState, breaking the
    // invariant this pattern exists to preserve.
    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(); // throws: it's still PENDING

When to use it

  • When an object’s behavior changes according to its internal state, and that behavior spans several methods that all need to check the same state field.
  • When there are so many states and transitions that a single conditional, scattered across several methods, becomes hard to maintain.

When to avoid it

With two or three states that behave almost identically, an enum with a simple conditional is usually more direct than a class per state.

Benefits and drawbacks

BenefitsDrawbacks
Removes large conditionals scattered across several methodsIt can be overkill with few states and little behavioral difference between them
Each state lives in its own class: SHIPPED’s behavior is read and changed without touching PENDING’sAdds a new class per state
The transitions become explicit, instead of hidden in loose assignments to a field

Relationship with other patterns

  • It’s the object-oriented way to protect an aggregate’s valid transitions. The architecture topic uses the word invariant for exactly what keeps a shipped order from being cancelled here.
  • It’s implemented very similarly to Strategy — both delegate behavior to an interchangeable object — but with a different intent: Strategy is chosen by the client code; State is changed by the state objects themselves, through internal transitions.

Share this guide

Search by concept, pattern or practice.