// Design Patterns · 20
State
Let an object change its behaviour when its state changes.
How it fits together
- Order wraps / holds OrderState (current)
- Pending implements OrderState
- Paid implements OrderState
- Shipped implements OrderState
- Cancelled implements OrderState
- Pending creates Paid (Pay())
- Paid creates Shipped (Ship())
- Paid creates Cancelled (Cancel())
- Pending creates Cancelled (Cancel())
Spot it
- switch (status) repeated in every method
- Cancelled orders that still get shipped
- Adding a status means editing every method
It costs
A class per state; overkill for two states and one transition.
In code
public interface IOrderState{ IOrderState Pay(Order order) => throw new InvalidTransition(this, nameof(Pay)); IOrderState Ship(Order order) => throw new InvalidTransition(this, nameof(Ship)); IOrderState Cancel(Order order) => throw new InvalidTransition(this, nameof(Cancel));}public sealed class Pending : IOrderState{ public IOrderState Pay(Order order) => new Paid(); public IOrderState Cancel(Order order) => new Cancelled();}public sealed class Paid : IOrderState{ public IOrderState Ship(Order order) => new Shipped(); public IOrderState Cancel(Order order) => new Cancelled();}public sealed class Order{ private IOrderState _state = new Pending(); public void Pay() => _state = _state.Pay(this); public void Ship() => _state = _state.Ship(this); public void Cancel() => _state = _state.Cancel(this);}Go deeperThe full explanation and what to ask for · 1 min readExpandCollapse
The context keeps a reference to its current state object and delegates every state-dependent call to it. Each state implements the operations that are legal from there and picks the next state, so the switch statements disappear and each transition sits in one obvious place.
For a handful of states with simple rules, an enum plus a transition table is clearer than a class per state. Whatever the shape, persist the state as data rather than as a class name, and make illegal transitions fail loudly instead of doing nothing.
State and Strategy share a structure. In Strategy the client picks the algorithm and it rarely changes; in State the states replace each other as the object moves through its lifecycle. A long-running process across services is the same idea, as a saga with a persisted state machine.
Ask for it
- state machine
- context
- state object
- transition
- guard
- finite states
Order has switch (Status) in Pay, Ship and Cancel. Model each status as an OrderState (Pending, Paid, Shipped, Cancelled) that implements those three methods and returns the next state; illegal moves throw InvalidTransition. Order delegates to its current state. Test every transition.