loader

Ever used a vending machine that seems to know exactly what to do depending on whether you’ve inserted a coin or not? That’s the State Pattern in action! It allows an object to change its behavior when its internal state changes—just like a vending machine that acts differently when it’s waiting for a coin, dispensing an item, or running out of snacks.

Why Bother with It?

  • Dynamic Behavior: Just as a vending machine responds differently when you insert a coin versus when it’s empty, the State Pattern lets your objects alter their behavior based on their current state.
  • Clean Separation: Each state is encapsulated in its own class. This means you can modify or add new behaviors without cluttering your main code.
  • Easy Maintenance: With states separated, updating or fixing one behavior doesn’t affect the others. It’s like having a machine that only needs a tune-up on its coin slot when it’s misbehaving—not the entire system!

How It Works (In Plain English)

  1. Different States for Different Modes: Think of a vending machine that can be in various states: waiting for a coin, coin inserted, dispensing an item, or sold out.
  2. State Objects: Each state is represented by its own class. They define what happens when you interact with the machine in that particular state.
  3. The Context (Vending Machine): The vending machine keeps a reference to its current state and delegates requests (like inserting a coin or pressing a button) to that state. When something changes (like an item is dispensed), the machine switches to a new state.
  4. Smooth Transitions: As you interact with the machine, it smoothly transitions from one state to another—ensuring that your experience is always consistent with the current situation.

C# Code Example

C#
using System;

public interface IVendingMachineState {
    void InsertCoin();
    void PressButton();
    void Dispense();
}

public class NoCoinState : IVendingMachineState {
    private VendingMachine _machine;
    public NoCoinState(VendingMachine machine) {
        _machine = machine;
    }
    public void InsertCoin() {
        Console.WriteLine("Coin inserted. You can now press the button.");
        _machine.SetState(_machine.HasCoinState);
    }
    public void PressButton() {
        Console.WriteLine("Insert a coin first.");
    }
    public void Dispense() {
        Console.WriteLine("Nothing to dispense.");
    }
}

public class HasCoinState : IVendingMachineState {
    private VendingMachine _machine;
    public HasCoinState(VendingMachine machine) {
        _machine = machine;
    }
    public void InsertCoin() {
        Console.WriteLine("Coin already inserted.");
    }
    public void PressButton() {
        Console.WriteLine("Button pressed. Dispensing item...");
        _machine.SetState(_machine.SoldState);
    }
    public void Dispense() {
        Console.WriteLine("Press the button to dispense.");
    }
}

public class SoldState : IVendingMachineState {
    private VendingMachine _machine;
    public SoldState(VendingMachine machine) {
        _machine = machine;
    }
    public void InsertCoin() {
        Console.WriteLine("Please wait, dispensing your item.");
    }
    public void PressButton() {
        Console.WriteLine("Already dispensing, please wait.");
    }
    public void Dispense() {
        Console.WriteLine("Item dispensed. Enjoy!");
        _machine.ItemCount--;
        if (_machine.ItemCount > 0)
            _machine.SetState(_machine.NoCoinState);
        else {
            Console.WriteLine("Out of items!");
            _machine.SetState(_machine.SoldOutState);
        }
    }
}

public class SoldOutState : IVendingMachineState {
    private VendingMachine _machine;
    public SoldOutState(VendingMachine machine) {
        _machine = machine;
    }
    public void InsertCoin() {
        Console.WriteLine("Machine is sold out.");
    }
    public void PressButton() {
        Console.WriteLine("Machine is sold out.");
    }
    public void Dispense() {
        Console.WriteLine("Machine is sold out.");
    }
}

public class VendingMachine {
    public IVendingMachineState NoCoinState { get; private set; }
    public IVendingMachineState HasCoinState { get; private set; }
    public IVendingMachineState SoldState { get; private set; }
    public IVendingMachineState SoldOutState { get; private set; }
    
    private IVendingMachineState _currentState;
    public int ItemCount { get; set; }
    
    public VendingMachine(int itemCount) {
        ItemCount = itemCount;
        NoCoinState = new NoCoinState(this);
        HasCoinState = new HasCoinState(this);
        SoldState = new SoldState(this);
        SoldOutState = new SoldOutState(this);
        _currentState = itemCount > 0 ? NoCoinState : SoldOutState;
    }
    
    public void SetState(IVendingMachineState state) {
        _currentState = state;
    }
    
    public void InsertCoin() {
        _currentState.InsertCoin();
    }
    
    public void PressButton() {
        _currentState.PressButton();
        _currentState.Dispense();
    }
}

public class Program {
    public static void Main() {
        VendingMachine machine = new VendingMachine(2);
        
        machine.PressButton(); // Should tell you to insert a coin.
        machine.InsertCoin();
        machine.PressButton(); // Dispenses first item.
        
        machine.InsertCoin();
        machine.PressButton(); // Dispenses second item.
        
        machine.InsertCoin();  // Machine is sold out now.
        machine.PressButton();
    }
}

The Takeaway

The State Pattern is like a smart vending machine that adjusts its behavior based on what’s happening inside—waiting for a coin, dispensing an item, or letting you know it’s sold out. By encapsulating states into separate objects, it keeps your code organized, flexible, and easier to maintain. So next time you need your object to change behavior dynamically, think of a vending machine and let the State Pattern do its thing!

Happy coding—and may your snacks always be within reach!