Let an object change its behavior when its internal state changes, by swapping out a state object instead of branching on a flag.
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.
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` always calls `this.state.ship()` — it never checks a status string. Each state decides what transitions are valid from itself.
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