Skip to content

Lifecycle Management & State Transitions

Behaviour changes when internal state changes

Builds on Strategy Pattern: Dynamic Runtime Interchangeability and Enums

Stage 3core2 min read

The refactored code successfully transitions the system to the State Design Pattern, a behavioral design pattern that lets an object change its behavior when its internal state changes. The logic for each state sits in its own class. So the main class, the context, stays small and rarely needs edits.


1. How This Approach Solves the Issues

Instead of one big block of conditionals, each state now has its own class (encapsulation), and the main object passes requests to the current state (delegation). This removes the stiff, string-based workflow.

IssueSolution with State Pattern
State Transition ManagementEach concrete state class now handles its own transitions. You can add a state or change one transition rule without editing the OrderContext class.
Lack of EncapsulationEach lifecycle stage has its own class. Each class has one job (SRP), which makes the code easier to maintain.
Code DuplicationThe rules for each stage, including whether cancelling is allowed, sit in that stage's class. The same condition checks are no longer repeated in several methods.
Flexibility for Future ChangesTo add a state, write a new class that implements the OrderState interface. The rest of the code stays untouched. This follows the Open/Closed Principle (OCP): open to new features, closed to edits.

2. When to Use the State Pattern

Use the State Pattern in these cases:

  • Object Behavior Depends on Internal State: When what an object does depends on its current condition or phase.
  • Well-Defined and Finite State Transitions: When the lifecycle is clear and has a limited number of paths from one phase to the next.
  • Avoiding Complex Conditionals: When you want to remove long, error-prone if-else or switch-case blocks that check a status before each action.
  • Need for Explicit State Governance: When you need clear rules for how transitions happen and who triggers them.
  • Distinct Behavior for Each State: When each step has its own business rules, so it makes sense to give each step its own class.

3. Structural Breakdown

ComponentResponsibility
Context (OrderContext)Gives clients the methods to call, holds the current state, and passes each request to it.
State Interface (OrderState)Lists the methods every state must have, one for each action in the lifecycle.
Concrete States (OrderPlacedState, etc.)Each one holds the rules for its stage and moves the context to the next state.

4. Summary of the "Context-State" Relationship

Think of the State Pattern like a Smartphone Screen. The physical buttons (the Context) never change. But when the phone goes from an Unlocked State to a Locked State, pressing the home button does something different. The button does not run a big switch to check if the phone is locked. It hands the click to the current screen state object. That object decides whether to open an app or show the lock screen.

In this topic

After code1 file

After/Main.java186 linesGitHub
package DesignPatterns.BehaviouralPatterns.StatePattern.After;import java.util.*;// ============================================================================// 1. THE CONTEXT CLASS// ============================================================================/** * The Context class acts as the interface to the outside world. It maintains a  * polymorphic reference to the active state object. Notice that it contains no  * conditional branches or state transition rules itself; it delegates all requests * to its internal state implementation object. */class OrderContext {    // COMPOSITION: Reference to the State Interface abstraction    private OrderState currentState;    /**     * Constructor sets the initial, entry-level configuration.     */    public OrderContext() {        this.currentState = new OrderPlacedState(); // Default baseline state    }    /**     * State Mutator: Allows state objects to change the state of the context      * dynamically at runtime. This drives the actual transition wheel.     */    public void setState(OrderState state) {        this.currentState = state;    }    /**     * DELEGATION: Instead of running switch statements, the context simply says:     * "I don't know my state logic, handle this transaction for me."     */    public void next() {        currentState.next(this); // Passing 'this' lets the state object invoke context.setState()    }    public void cancel() {        currentState.cancel(this);    }    public String getCurrentState() {        return currentState.getStateName();    }}// ============================================================================// 2. STATE INTERFACE// ============================================================================/** * Defines the contract representing every legal business operation or state transition  * trigger that can occur within the lifecycle of the order process. */interface OrderState {    void next(OrderContext context);   // Request for sequential progression    void cancel(OrderContext context); // Request for transactional cancellation    String getStateName();             // Type identifier replacing loose magic strings}// ============================================================================// 3. CONCRETE STATES (Encapsulated Behaviors)// ============================================================================/** * Each concrete state class isolates the unique business rules for that specific stage. * It encapsulates what is valid, what is blocked, and which state follows next. */class OrderPlacedState implements OrderState {    @Override    public void next(OrderContext context) {        // Enforcing a precise structural move from PLACED to PREPARING        context.setState(new PreparingState());        System.out.println("Order is now being prepared.");    }    @Override    public void cancel(OrderContext context) {        // Cancellation is completely valid during initial placement phases        context.setState(new CancelledState());        System.out.println("Order has been cancelled.");    }    @Override    public String getStateName() {        return "ORDER_PLACED";    }}class PreparingState implements OrderState {    @Override    public void next(OrderContext context) {        context.setState(new OutForDeliveryState());        System.out.println("Order is out for delivery.");    }    @Override    public void cancel(OrderContext context) {        // Cancellation is still permissible while items are being packaged        context.setState(new CancelledState());        System.out.println("Order has been cancelled.");    }    @Override    public String getStateName() {        return "PREPARING";    }}class OutForDeliveryState implements OrderState {    @Override    public void next(OrderContext context) {        context.setState(new DeliveredState());        System.out.println("Order has been delivered.");    }    @Override    public void cancel(OrderContext context) {        // STATE SANITIZATION RULE: Guard claw blocks cancellations once on transit vehicles        System.out.println("Cannot cancel. Order is out for delivery.");    }    @Override    public String getStateName() {        return "OUT_FOR_DELIVERY";    }}class DeliveredState implements OrderState {    @Override    public void next(OrderContext context) {        // TERMINAL STATUS GUARD: Endpoint reached, can go no further        System.out.println("Order is already delivered.");    }    @Override    public void cancel(OrderContext context) {        System.out.println("Cannot cancel a delivered order.");    }    @Override    public String getStateName() {        return "DELIVERED";    }}class CancelledState implements OrderState {    @Override    public void next(OrderContext context) {        // TERMINAL STATUS GUARD: Cancelled tracks cannot rejoin active distribution runs        System.out.println("Cancelled order cannot move to next state.");    }    @Override    public void cancel(OrderContext context) {        System.out.println("Order is already cancelled.");    }    @Override    public String getStateName() {        return "CANCELLED";    }}// ============================================================================// 4. CLIENT INTERACTION// ============================================================================public class Main {    public static void main(String[] args) {        OrderContext order = new OrderContext();        System.out.println("Current State: " + order.getCurrentState());        // Drive structural sequence        order.next();  // ORDER_PLACED -> PREPARING        order.next();  // PREPARING -> OUT_FOR_DELIVERY                // Asserting validation guards        order.cancel(); // Evaluates against OutForDeliveryState logic -> Blocked!                order.next();  // OUT_FOR_DELIVERY -> DELIVERED        order.cancel(); // Evaluates against DeliveredState logic     -> Blocked!        System.out.println("Final State: " + order.getCurrentState());    }}

Done with this topic?

How well do you know it?

PreviousEncapsulating Requests