State Pattern

The State Pattern is a behavioral design pattern that allows an object to alter its behavior when its internal state changes. This pattern is particularly useful when an object’s behavior depends on its state and changes dynamically based on internal conditions. At its core, the State Pattern enables an object to delegate state-specific behavior to its current state object. This approach promotes cleaner code by encapsulating state-specific logic into separate classes rather than scattering conditional statements throughout the object’s methods. This separation enhances maintainability and extensibility by isolating the effects of state transitions.

Key Components

  1. Context: The class that maintains an instance of a state and delegates state-specific behavior to it. It provides methods for clients to interact with and change the state.
  2. State: An interface or abstract class defining methods for state-specific behavior.
  3. Concrete State: Classes implementing the State interface, each encapsulating the behavior for a specific state.
  4. Client: Interacts with the Context, triggering actions that may lead to state transitions.

How It Works

  • The Context holds a reference to a State object representing its current state.
  • The State interface defines methods for actions that vary by state.
  • Each Concrete State implements these methods to provide state-specific behavior and may trigger transitions to other states by updating the Context’s state reference.
  • The Client calls methods on the Context, which delegates to the current state, allowing behavior to change dynamically as the state changes.
  • State transitions can be managed by the Context, the Concrete States, or both, depending on the design.

Sample Implementation

Let’s model a vending machine that dispenses items based on its state: NoCoin (waiting for a coin), HasCoin (coin inserted, ready to dispense), and Sold (item dispensed, resetting). The machine’s behavior changes depending on its state.

// State Interface
interface VendingMachineState {
    void insertCoin(VendingMachine machine);
    void pressButton(VendingMachine machine);
    void dispenseItem(VendingMachine machine);
}

// Context
class VendingMachine {
    private VendingMachineState state;
    private int items;

    public VendingMachine(int items) {
        this.items = items;
        this.state = new NoCoinState(); // Initial state
        System.out.println("Vending Machine initialized with " + items + " items.");
    }

    public void setState(VendingMachineState state) {
        this.state = state;
    }

    public void insertCoin() {
        state.insertCoin(this);
    }

    public void pressButton() {
        state.pressButton(this);
    }

    public void dispenseItem() {
        state.dispenseItem(this);
    }

    public int getItems() {
        return items;
    }

    public void setItems(int items) {
        this.items = items;
    }
}

// Concrete States
class NoCoinState implements VendingMachineState {
    @Override
    public void insertCoin(VendingMachine machine) {
        System.out.println("Coin inserted.");
        machine.setState(new HasCoinState());
    }

    @Override
    public void pressButton(VendingMachine machine) {
        System.out.println("No coin inserted. Please insert a coin first.");
    }

    @Override
    public void dispenseItem(VendingMachine machine) {
        System.out.println("No coin inserted. Cannot dispense item.");
    }
}

class HasCoinState implements VendingMachineState {
    @Override
    public void insertCoin(VendingMachine machine) {
        System.out.println("Coin already inserted. Please press the button.");
    }

    @Override
    public void pressButton(VendingMachine machine) {
        System.out.println("Button pressed.");
        machine.setState(new SoldState());
        machine.dispenseItem();
    }

    @Override
    public void dispenseItem(VendingMachine machine) {
        System.out.println("Press the button to dispense an item.");
    }
}

class SoldState implements VendingMachineState {
    @Override
    public void insertCoin(VendingMachine machine) {
        System.out.println("Item already dispensed. Please wait.");
    }

    @Override
    public void pressButton(VendingMachine machine) {
        System.out.println("Item already dispensed. Please insert another coin.");
    }

    @Override
    public void dispenseItem(VendingMachine machine) {
        if (machine.getItems() > 0) {
            System.out.println("Dispensing item...");
            machine.setItems(machine.getItems() - 1);
            System.out.println("Items remaining: " + machine.getItems());
            machine.setState(machine.getItems() > 0 ? new NoCoinState() : new NoCoinState());
        } else {
            System.out.println("Out of items!");
            machine.setState(new NoCoinState());
        }
    }
}

// Usage
public class Main {
    public static void main(String[] args) {
        // Create vending machine with 2 items
        VendingMachine machine = new VendingMachine(2);

        // Simulate interactions
        System.out.println("\nScenario 1: Normal operation");
        machine.insertCoin();
        machine.pressButton();

        System.out.println("\nScenario 2: Invalid action (press button without coin)");
        machine.pressButton();

        System.out.println("\nScenario 3: Another valid operation");
        machine.insertCoin();
        machine.pressButton();

        System.out.println("\nScenario 4: Out of items");
        machine.insertCoin();
        machine.pressButton();
    }
}

/*
Vending Machine initialized with 2 items.

Scenario 1: Normal operation
Coin inserted.
Button pressed.
Dispensing item...
Items remaining: 1

Scenario 2: Invalid action (press button without coin)
No coin inserted. Please insert a coin first.

Scenario 3: Another valid operation
Coin inserted.
Button pressed.
Dispensing item...
Items remaining: 0

Scenario 4: Out of items
Coin inserted.
Button pressed.
Out of items!
*/Code language: PHP (php)

Use case and Implementation

Managing different states of an account (e.g., active, suspended, closed) and changing behavior accordingly.

//StatePatternDemo.java
// State interface
interface AccountState {
    void deposit(double amount);
    void withdraw(double amount);
    void freeze();
    void close();
}
// Concrete state classes
class ActiveState implements AccountState {
    @Override
    public void deposit(double amount) {
        System.out.println("Depositing $" + amount + " into the active account.");
    }
    @Override
    public void withdraw(double amount) {
        System.out.println("Withdrawing $" + amount + " from the active account.");
    }
    @Override
    public void freeze() {
        System.out.println("Freezing the active account.");
        // Transition to FrozenState if implementation requires
    }
    @Override
    public void close() {
        System.out.println("Closing the active account.");
        // Transition to ClosedState if implementation requires
    }
}
class FrozenState implements AccountState {
    @Override
    public void deposit(double amount) {
        System.out.println("Cannot deposit. Account is frozen.");
    }

    @Override
    public void withdraw(double amount) {
        System.out.println("Cannot withdraw. Account is frozen.");
    }

    @Override
    public void freeze() {
        System.out.println("Account is already frozen.");
    }
    @Override
    public void close() {
        System.out.println("Closing the frozen account.");
        // Transition to ClosedState if implementation requires
    }
}
class ClosedState implements AccountState {
    @Override
    public void deposit(double amount) {
        System.out.println("Cannot deposit. Account is closed.");
    }
    @Override
    public void withdraw(double amount) {
        System.out.println("Cannot withdraw. Account is closed.");
    }
    @Override
    public void freeze() {
        System.out.println("Cannot freeze. Account is closed.");
    }
    @Override
    public void close() {
        System.out.println("Account is already closed.");
    }
}
// Context class
class Account {
    private AccountState state;
    public Account() {
        // Initially, account is active
        this.state = new ActiveState();
    }
    public void setState(AccountState state) {
        this.state = state;
    }
    public void deposit(double amount) {
        state.deposit(amount);
    }
    public void withdraw(double amount) {
        state.withdraw(amount);
    }
    public void freeze() {
        state.freeze();
    }
    public void close() {
        state.close();
    }
}

// Main class for testing
public class StatePatternDemo {
    public static void main(String[] args) {
        Account account = new Account();
        // Test active state operations
        account.deposit(100.0);
        account.withdraw(50.0);
        account.freeze();
        account.close();
        // Test frozen state operations
        account.deposit(100.0); // Should fail
        account.withdraw(50.0); // Should fail
        account.freeze(); // Should say already frozen
        account.close();
        // Test closed state operations
        account.deposit(100.0); // Should fail
        account.withdraw(50.0); // Should fail
        account.freeze(); // Should fail
        account.close(); // Should say already closed
    }
}

/*
C:\>javac StatePatternDemo.java

C:\>java StatePatternDemo
Depositing $100.0 into the active account.
Withdrawing $50.0 from the active account.
Freezing the active account.
Closing the active account.
Depositing $100.0 into the active account.
Withdrawing $50.0 from the active account.
Freezing the active account.
Closing the active account.
Depositing $100.0 into the active account.
Withdrawing $50.0 from the active account.
Freezing the active account.
Closing the active account.
*/

Advantages

  • Encapsulation: State-specific behavior is encapsulated in separate classes, improving readability and maintainability.
  • Single Responsibility: Each state class handles one state, adhering to the Single Responsibility Principle.
  • Flexibility: New states can be added without modifying the Context or other states.
  • Clear State Transitions: State transitions are explicit, making state machines easier to understand.
  • Eliminates Conditionals: Replaces complex if-else or switch statements with polymorphic state objects.

Disadvantages

  • Class Proliferation: Each state requires a new class, which can lead to many classes for complex state machines.
  • Complexity: Adds overhead for simple state-dependent behavior that could be handled with conditionals.
  • State Management: Managing state transitions and ensuring correctness can be challenging in large systems.
  • Overhead: Creating state objects may introduce slight performance overhead.

When to Use

  • When an object’s behavior depends on its state, and the behavior changes significantly across states.
  • When you want to avoid complex conditional logic for state-dependent behavior.
  • When implementing a state machine with well-defined states and transitions.
  • When you need to make state-specific behavior extensible or maintainable.

Real-World Example

  • Vending Machines: As shown, behavior changes based on states like no coin, has coin, or sold.
  • Order Processing: An order transitions through states (e.g., pending, shipped, delivered), with different actions available in each state.
  • TCP Connections: A connection moves through states like listening, established, or closed, with state-specific behavior.
  • Game Characters: A character’s behavior (e.g., idle, running, attacking) changes based on its state.

The State Pattern is a behavioral design pattern that allows an object to alter its behavior when its internal state changes, making the object appear to change its class. It encapsulates state-specific behavior into separate classes and delegates the behavior based on the current state.

This pattern promotes flexibility, cleaner code, and better organization by avoiding large conditional statements (like if-else or switch) scattered throughout the code. Instead, it uses polymorphism to handle state transitions and behavior variations.

Scroll to Top