Behavioral · DWG NO. 022

State

Let an object change its behavior when its internal state changes, by swapping out a state object instead of branching on a flag.

TypeBehavioral
ScopeSingle object, multiple states
ComplexityMedium
Common inOrder status, media players, connection lifecycles

One method with a giant switch on a status field

An order's `ship()` method that does `if (status === 'paid') ... else if (status === 'shipped') throw ... else if (status === 'cancelled') throw ...` re-implements the same switch in every method (`cancel()`, `refund()`, ...) and it's easy for one method's switch to fall out of sync with another's.

Each state is an object with its own behavior

The context (Order) delegates its behavior to a current state object. Each state class implements the same methods (ship, cancel) but only some of them are valid — invalid ones simply refuse or the state controls the transition to the next state object.

Order(context)PaidStateship()ShippedStatecancel()CancelledState

Order lifecycle as swappable state objects

`Order` always calls `this.state.ship()` — it never checks a status string. Each state decides what transitions are valid from itself.

order-state.js
class PaidState {
  ship(order) { console.log("shipping order"); order.setState(new ShippedState()); }
  cancel(order) { console.log("cancelling order"); order.setState(new CancelledState()); }
}
class ShippedState {
  ship() { console.log("already shipped"); }
  cancel() { console.log("cannot cancel — already shipped"); }
}
class CancelledState {
  ship() { console.log("cannot ship — order cancelled"); }
  cancel() { console.log("already cancelled"); }
}

class Order {
  constructor() { this.state = new PaidState(); }
  setState(state) { this.state = state; }
  ship() { this.state.ship(this); }
  cancel() { this.state.cancel(this); }
}

const order = new Order();
order.ship();    // shipping order
order.cancel();  // cannot cancel — already shipped

Trade-offs