Fundamentals 15 min read

State Pattern Explained: Turning Conditional Logic into Flowing State Transitions

The article demonstrates how the State design pattern replaces tangled if‑else state checks with dedicated state classes, using an order lifecycle example to compare it with Strategy, explain role responsibilities, transition driving mechanisms, and guidelines for when to apply the pattern.

Dabaoshi
Dabaoshi
Dabaoshi
State Pattern Explained: Turning Conditional Logic into Flowing State Transitions

Problem with if‑else state checks

In an order object a status string determines which operation (pay, cancel, ship, …) is allowed. The naïve implementation uses a series of if‑else blocks inside each method:

public class Order {
    private String status; // "待付款", "已付款", "已发货", "已完成", "已取消"
    public void pay() {
        if (status.equals("待付款")) {
            status = "已付款";
        } else {
            throw new RuntimeException("当前状态不能支付");
        }
    }
    public void cancel() {
        if (status.equals("待付款")) {
            status = "已取消";
        } else if (status.equals("已付款")) {
            status = "已取消";
        } else {
            throw new RuntimeException("当前状态不能取消");
        }
    }
    public void ship() {
        if (status.equals("已付款")) {
            status = "已发货";
        } else {
            throw new RuntimeException("当前状态不能发货");
        }
    }
    // ... each operation repeats state checks
}

As the number of states and operations grows the code becomes:

Long, repetitive if‑else blocks in every method.

Transition rules scattered across many methods, making it hard to see the complete behavior of a single state.

A violation of the Open/Closed Principle – adding a new state requires editing every operation.

Prone to illegal transitions because the compiler cannot enforce state‑machine rules.

Object‑orienting each state

The State pattern introduces a state interface that declares all possible operations. Each concrete state class implements the interface and encapsulates the behavior and transition logic for that particular state.

public interface OrderState {
    void pay(OrderContext ctx);
    void cancel(OrderContext ctx);
    void ship(OrderContext ctx);
    // ... other operations
}

Example concrete state – PendingPayState:

public class PendingPayState implements OrderState {
    public void pay(OrderContext ctx) {
        System.out.println("支付成功");
        ctx.setState(new PaidState()); // transition to "已付款"
    }
    public void cancel(OrderContext ctx) {
        System.out.println("订单已取消");
        ctx.setState(new CancelledState()); // transition to "已取消"
    }
    public void ship(OrderContext ctx) {
        throw new RuntimeException("未付款不能发货");
    }
}
// PaidState, ShippedState, CompletedState, CancelledState are analogous

The context holds the current state and forwards calls:

public class OrderContext {
    private OrderState state = new PendingPayState(); // initial state
    public void setState(OrderState state) { this.state = state; }
    public void pay()   { state.pay(this); }
    public void cancel(){ state.cancel(this); }
    public void ship()  { state.ship(this); }
}

Usage demonstrates automatic behavior change and transition:

OrderContext order = new OrderContext(); // starts in PendingPayState
order.pay();   // prints "支付成功" → moves to PaidState
order.ship();  // prints "已发货" → moves to ShippedState
order.cancel(); // throws RuntimeException: 已发货不能取消

All rules for a given state are now localized in a single class, eliminating conditional branches in the context.

Roles and who drives the transition

Context – OrderContext. Holds the current OrderState and delegates operations.

Abstract State – OrderState interface. Declares the operations shared by all states.

Concrete State – e.g., PendingPayState, PaidState. Encapsulates behavior and transition logic for a specific state.

Two ways to trigger a transition:

State‑driven transition : each concrete state calls ctx.setState(...) after handling an operation. This keeps the rule close to the state but introduces compile‑time dependencies between state classes.

Context‑driven transition : the state returns a result and the context decides the next state. This reduces coupling between states at the cost of a heavier context.

For a fixed order lifecycle the state‑driven approach is typical; for highly dynamic workflows the context‑driven approach may be preferable.

State vs. Strategy: a twin analysis

Both patterns share a similar class diagram (Context holds an interface reference and delegates behavior), yet they differ in intent and usage:

Relationship between implementations : Strategy algorithms are independent and parallel; State objects are linked and flow from one to another.

Who selects the implementation : Strategies are chosen externally by the client (e.g., setStrategy(new WeightShipping())); States transition automatically based on business rules.

Frequency of switching : A Strategy is usually selected once and remains unchanged during the operation; a State switches repeatedly throughout the object's lifecycle.

Mnemonic: Strategy = "pick one algorithm horizontally"; State = "flow through stages vertically".

Practical tip: if you can draw a state‑transition diagram with arrows between implementations, you are dealing with the State pattern; if you can only list independent options, it is a Strategy.

When to apply the State pattern

Use the State pattern when:

An object's behavior clearly depends on its internal state and the behavior differs significantly between states.

Explicit state‑transition rules can be visualized as a state‑machine diagram.

The code contains many if‑else / switch checks on a state field, and both the number of states and operations are expected to grow.

Avoid the pattern when:

Only a few states exist and behavior differences are minor – a simple if‑else or enum is sufficient.

The alternatives are parallel options without transition relationships – a Strategy is more appropriate.

The state machine becomes extremely large (dozens of states, hundreds of transitions) – a dedicated state‑machine framework such as Spring StateMachine may be more maintainable.

Summary

The State pattern converts scattered conditional logic into a set of concrete state classes with explicit transition relationships. Adding a new state typically requires only a new class, leaving existing code untouched and preventing illegal transitions. Although its structural diagram resembles Strategy, the intent differs: Strategy selects one independent algorithm, while State represents a sequence of linked stages that automatically flow according to business rules.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaStrategy PatternState MachineObject-OrientedState Pattern
Dabaoshi
Written by

Dabaoshi

Practical utilities

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.