Design Pattern

State Pattern

Clean Java-only production-ready implementation.


An object changes its behavior when its internal state changes. Without State, you get giant if-else chains checking state in every method. With State, each state is a class with its own behavior. Adding a new state means one new class, not modifying every method.

class VendingMachine {
    String state = "IDLE";

    void insertCoin() {
        if (state.equals("IDLE")) { state = "HAS_MONEY"; }
        else if (state.equals("HAS_MONEY")) { /* already has money */ }
        else if (state.equals("DISPENSING")) { /* wait */ }
    }

    void selectItem(String item) {
        if (state.equals("IDLE")) { /* no money */ }
        else if (state.equals("HAS_MONEY")) { state = "DISPENSING"; }
        else if (state.equals("DISPENSING")) { /* already dispensing */ }
    }

    // Every method needs the same if-else chain.
    // Adding "MAINTENANCE" state means editing every method.
}
// ─── EXAMPLE 1 ──────────────────────────────────────────────────────────────
// WHAT WE ARE IMPLEMENTING:
// A vending machine controlling item selection and coin insertion
// transitions.
//
// WHERE THE STATE FITS IN:
// VendingMachineState is the State interface. HasCoinState and NoCoinState
// represent Concrete States. VendingMachine acts as the Context tracking
// states.
// ────────────────────────────────────────────────────────────────────────────
// --- State interface ---
interface VendingMachineState {
    void insertCoin(VendingMachine vm);
    void selectItem(VendingMachine vm, String item);
    void dispense(VendingMachine vm);
}

// --- Context ---
class VendingMachine {
    private VendingMachineState state;
    private int balance = 0;

    public VendingMachine() {
        state = new IdleState();
    }

    void setState(VendingMachineState state) {
        System.out.println("  [State] " + this.state.getClass().getSimpleName() + " -> " + state.getClass().getSimpleName());
        this.state = state;
    }
    void setBalance(int b) { this.balance = b; }
    int getBalance() { return balance; }

    public void insertCoin() { state.insertCoin(this); }
    public void selectItem(String item) { state.selectItem(this, item); }
    public void dispense() { state.dispense(this); }
}

// --- Concrete states ---
class IdleState implements VendingMachineState {
    public void insertCoin(VendingMachine vm) {
        System.out.println("  Coin inserted: $5");
        vm.setBalance(5);
        vm.setState(new HasMoneyState());
    }
    public void selectItem(VendingMachine vm, String item) {
        System.out.println("  Insert coin first");
    }
    public void dispense(VendingMachine vm) {
        System.out.println("  Insert coin first");
    }
}

class HasMoneyState implements VendingMachineState {
    public void insertCoin(VendingMachine vm) {
        System.out.println("  Coin already inserted. Balance: $" + vm.getBalance());
    }
    public void selectItem(VendingMachine vm, String item) {
        System.out.println("  Item selected: " + item);
        if (vm.getBalance() >= 5) {
            vm.setState(new DispensingState());
        } else {
            System.out.println("  Insufficient balance");
        }
    }
    public void dispense(VendingMachine vm) {
        System.out.println("  Select item first");
    }
}

class DispensingState implements VendingMachineState {
    public void insertCoin(VendingMachine vm) {
        System.out.println("  Dispensing in progress");
    }
    public void selectItem(VendingMachine vm, String item) {
        System.out.println("  Dispensing in progress");
    }
    public void dispense(VendingMachine vm) {
        System.out.println("  Dispensing item... Enjoy!");
        vm.setBalance(0);
        vm.setState(new IdleState());
    }
}

public class Main {
    public static void main(String[] args) {
        VendingMachine vm = new VendingMachine();

        // Full flow
        vm.insertCoin();        // Idle -> HasMoney
        vm.selectItem("Coke");  // HasMoney -> Dispensing
        vm.dispense();          // Dispensing -> Idle

        // Edge cases
        vm.dispense();          // Idle -> "Insert coin first"
        vm.selectItem("Chips"); // Idle -> "Insert coin first"
    }
}