The State Pattern allows an object to alter its behavior when its internal state changes, making it appear as though the object has changed its class. Instead of scattering state-dependent logic across methods with conditionals, you encapsulate each state into its own class, and the object delegates behavior to its current state.
If you have ever seen a method riddled with if (status == "pending") checks scattered throughout a class, the State Pattern is the clean alternative.
You are building an order management system. An order moves through several states: Pending, Processing, Shipped, and Delivered. Each state allows different actions — you can cancel a pending order but not a shipped one. You can ship a processing order but not a delivered one.
The naive approach is to add status checks in every method: cancel() checks if status is “pending,” ship() checks if status is “processing,” and so on. As states and transitions grow, the class becomes a tangled web of conditionals. Adding a new state (like “Returned”) means touching every method.
The State Pattern creates a separate class for each state. Each state class implements the same interface and defines what actions are valid in that state. The order object (context) holds a reference to its current state and delegates all actions to it. State transitions happen by swapping the current state object.
This eliminates conditional logic and makes each state’s behavior self-contained. Adding a new state means adding a new class — no existing state classes need to change.
Encapsulate what varies. The thing that varies here is the behavior associated with each state. By extracting each state into its own class, you isolate state-specific logic and make transitions explicit and traceable.
Scenario: An order management system with state-driven behavior.
// State interface
public interface OrderState {
void next(OrderContext order);
void cancel(OrderContext order);
String getStatus();
}
// Context
public class OrderContext {
private OrderState state;
private final String orderId;
public OrderContext(String orderId) {
this.orderId = orderId;
this.state = new PendingState();
System.out.println("Order " + orderId + " created. Status: " + state.getStatus());
}
public void setState(OrderState state) {
this.state = state;
System.out.println("Order " + orderId + " status: " + state.getStatus());
}
public void next() {
state.next(this);
}
public void cancel() {
state.cancel(this);
}
public String getStatus() {
return state.getStatus();
}
public String getOrderId() {
return orderId;
}
}
// Concrete State: Pending
public class PendingState implements OrderState {
@Override
public void next(OrderContext order) {
System.out.println("Processing payment for order " + order.getOrderId() + "...");
order.setState(new ProcessingState());
}
@Override
public void cancel(OrderContext order) {
System.out.println("Order " + order.getOrderId() + " cancelled.");
order.setState(new CancelledState());
}
@Override
public String getStatus() {
return "PENDING";
}
}
// Concrete State: Processing
public class ProcessingState implements OrderState {
@Override
public void next(OrderContext order) {
System.out.println("Order " + order.getOrderId() + " shipped!");
order.setState(new ShippedState());
}
@Override
public void cancel(OrderContext order) {
System.out.println("Order " + order.getOrderId()
+ " is being processed. Initiating cancellation...");
order.setState(new CancelledState());
}
@Override
public String getStatus() {
return "PROCESSING";
}
}
// Concrete State: Shipped
public class ShippedState implements OrderState {
@Override
public void next(OrderContext order) {
System.out.println("Order " + order.getOrderId() + " delivered.");
order.setState(new DeliveredState());
}
@Override
public void cancel(OrderContext order) {
System.out.println("Cannot cancel order " + order.getOrderId()
+ " — already shipped.");
}
@Override
public String getStatus() {
return "SHIPPED";
}
}
// Concrete State: Delivered
public class DeliveredState implements OrderState {
@Override
public void next(OrderContext order) {
System.out.println("Order " + order.getOrderId()
+ " already delivered. No further transitions.");
}
@Override
public void cancel(OrderContext order) {
System.out.println("Cannot cancel order " + order.getOrderId()
+ " — already delivered.");
}
@Override
public String getStatus() {
return "DELIVERED";
}
}
// Concrete State: Cancelled
public class CancelledState implements OrderState {
@Override
public void next(OrderContext order) {
System.out.println("Order " + order.getOrderId()
+ " is cancelled. Cannot proceed.");
}
@Override
public void cancel(OrderContext order) {
System.out.println("Order " + order.getOrderId()
+ " is already cancelled.");
}
@Override
public String getStatus() {
return "CANCELLED";
}
}
// Usage
public class Main {
public static void main(String[] args) {
OrderContext order = new OrderContext("ORD-2001");
order.next(); // Pending -> Processing
order.next(); // Processing -> Shipped
order.cancel(); // Cannot cancel — already shipped
order.next(); // Shipped -> Delivered
order.next(); // Already delivered
System.out.println("\n--- Cancellation flow ---");
OrderContext order2 = new OrderContext("ORD-2002");
order2.cancel(); // Pending -> Cancelled
order2.next(); // Cannot proceed — cancelled
}
}
Same order management system in Python.
from abc import ABC, abstractmethod
# State interface
class OrderState(ABC):
@abstractmethod
def next(self, order: "OrderContext") -> None:
pass
@abstractmethod
def cancel(self, order: "OrderContext") -> None:
pass
@abstractmethod
def get_status(self) -> str:
pass
# Context
class OrderContext:
def __init__(self, order_id: str):
self.order_id = order_id
self._state: OrderState = PendingState()
print(f"Order {order_id} created. Status: {self._state.get_status()}")
def set_state(self, state: OrderState) -> None:
self._state = state
print(f"Order {self.order_id} status: {self._state.get_status()}")
def next(self) -> None:
self._state.next(self)
def cancel(self) -> None:
self._state.cancel(self)
@property
def status(self) -> str:
return self._state.get_status()
# Concrete State: Pending
class PendingState(OrderState):
def next(self, order: OrderContext) -> None:
print(f"Processing payment for order {order.order_id}...")
order.set_state(ProcessingState())
def cancel(self, order: OrderContext) -> None:
print(f"Order {order.order_id} cancelled.")
order.set_state(CancelledState())
def get_status(self) -> str:
return "PENDING"
# Concrete State: Processing
class ProcessingState(OrderState):
def next(self, order: OrderContext) -> None:
print(f"Order {order.order_id} shipped!")
order.set_state(ShippedState())
def cancel(self, order: OrderContext) -> None:
print(f"Order {order.order_id} is being processed. "
f"Initiating cancellation...")
order.set_state(CancelledState())
def get_status(self) -> str:
return "PROCESSING"
# Concrete State: Shipped
class ShippedState(OrderState):
def next(self, order: OrderContext) -> None:
print(f"Order {order.order_id} delivered.")
order.set_state(DeliveredState())
def cancel(self, order: OrderContext) -> None:
print(f"Cannot cancel order {order.order_id} — already shipped.")
def get_status(self) -> str:
return "SHIPPED"
# Concrete State: Delivered
class DeliveredState(OrderState):
def next(self, order: OrderContext) -> None:
print(f"Order {order.order_id} already delivered. "
f"No further transitions.")
def cancel(self, order: OrderContext) -> None:
print(f"Cannot cancel order {order.order_id} — already delivered.")
def get_status(self) -> str:
return "DELIVERED"
# Concrete State: Cancelled
class CancelledState(OrderState):
def next(self, order: OrderContext) -> None:
print(f"Order {order.order_id} is cancelled. Cannot proceed.")
def cancel(self, order: OrderContext) -> None:
print(f"Order {order.order_id} is already cancelled.")
def get_status(self) -> str:
return "CANCELLED"
# Usage
if __name__ == "__main__":
order = OrderContext("ORD-2001")
order.next() # Pending -> Processing
order.next() # Processing -> Shipped
order.cancel() # Cannot cancel — already shipped
order.next() # Shipped -> Delivered
order.next() # Already delivered
print("\n--- Cancellation flow ---")
order2 = OrderContext("ORD-2002")
order2.cancel() # Pending -> Cancelled
order2.next() # Cannot proceed — cancelled
if/else or switch statements that check the current state before acting.Thread class has states (NEW, RUNNABLE, BLOCKED, TERMINATED) that determine allowed operations.spring-statemachine project provides a framework for building state-based applications using this pattern.