Skip to content
Design patterns

State

An object changes its behaviour when its internal state changes, as if it had changed its class.

The idea

Many objects behave differently depending on the state they are in. A transaction that is pending, approved or cancelled responds differently to the same request. The usual way to code this is a switch or a chain of if statements on a status field, repeated in every method. Each new state means editing all of them.

The State pattern gives each state its own class, and all the state classes share one interface. The main object, the context, holds a reference to its current state and passes each request to it. To change its behaviour, the context swaps in a different state object.

From the outside, the context seems to change its class. Inside, the logic for each state sits in one place.

When to use it

  • An object's behaviour depends on its state, and it must change that behaviour at runtime.
  • Methods are full of switch or if statements on the same status field.
  • The object goes through a clear lifecycle, such as an order, a document review or a network connection.
  • You expect to add new states, and want each one in its own class.

Pros and cons

Pros

  • It is more flexible and has fewer branches: the if chains on the status go away.
  • Adding a new state is easy: write one class.
  • The code for each state sits in one class, so it is easier to read and to test.

Cons

  • The code is more complex, with one more class in the system for every state.
  • For an object with two or three simple states, a plain field and a switch can be clearer.
  • The transitions are spread across classes, so the full lifecycle is harder to see at a glance.

The example

The example is a payment transaction. A new transaction starts as pending. The demo then moves it to ApprovingState and then to PressedState, and each state handles the request in its own way.

  • State: the state. An abstract class that holds the transaction and declares handle().
  • Transaction: the context. It starts in PendingState, can switch state with setState(), and passes each request() to the current state's handle().
  • PendingState, ApprovingState, PressedState and CancelingState: concrete states. Each prints which state handled the request. The demo doesn't use CancelingState.
  • Demo: the client. It sends one request in each state.

Here, Demo moves the transaction from one state to the next. In the Gang of Four book, either the context or the state classes decide the next state. Each state already holds the transaction, so it could call setState() itself.

The code

State.javaJava
public abstract class State {
    protected Transaction transaction;

    public State(Transaction transaction) {
        this.transaction = transaction;
    }

    abstract void handle();
}
Transaction.javaJava
public class Transaction {
    private State state;

    public Transaction() {
        this.state = new PendingState(this);
    }

    public void setState(State state) {
        this.state = state;
    }

    public void request() {
        this.state.handle();
    }
}
PendingState.javaJava
public class PendingState extends State {
    public PendingState(Transaction transaction) {
        super(transaction);
    }

    @Override
    void handle() {
        // Your implementation for pending state transaction.
        System.out.println("handle pending state transaction");
    }
}
ApprovingState.javaJava
public class ApprovingState extends State {
    public ApprovingState(Transaction transaction) {
        super(transaction);
    }

    @Override
    void handle() {
        // Your implementation for approved state transaction.
        System.out.println("handle approved state transaction");
    }
}
PressedState.javaJava
public class PressedState extends State {
    public PressedState(Transaction transaction) {
        super(transaction);
    }

    @Override
    void handle() {
        // Your implementation for pressed state transaction.
        System.out.println("handle pressed state transaction");
    }
}
CancelingState.javaJava
public class CancelingState extends State {
    public CancelingState(Transaction transaction) {
        super(transaction);
    }

    @Override
    void handle() {
        // Your implementation for canceled state transaction.
        System.out.println("handle canceled state transaction");
    }
}
Demo.javaJava
public class Demo {
    public static void main(String[] args) {
        Transaction transaction = new Transaction();
        transaction.request();

        transaction.setState(new ApprovingState(transaction));
        transaction.request();

        transaction.setState(new PressedState(transaction));
        transaction.request();
    }
}

Running Demo prints:

handle pending state transaction
handle approved state transaction
handle pressed state transaction