You’re building a vending machine. It behaves differently when it has no money, has money, is dispensing, or is out of stock. Using if/else statements for every state creates complex, hard-to-maintain code. The State pattern lets an object alter its behavior when its internal state changes, making state-specific behavior explicit and maintainable.
Problem
Consider a vending machine:
public class VendingMachine
{
private string _state = "NoMoney";
public void InsertMoney()
{
if (_state == "NoMoney")
{
_state = "HasMoney";
Console.WriteLine("Money inserted");
}
else if (_state == "HasMoney")
{
Console.WriteLine("Money already inserted");
}
else if (_state == "Dispensing")
{
Console.WriteLine("Please wait, dispensing...");
}
// ... more states
}
public void SelectProduct()
{
if (_state == "NoMoney")
{
Console.WriteLine("Please insert money first");
}
else if (_state == "HasMoney")
{
_state = "Dispensing";
Console.WriteLine("Dispensing product...");
}
// ... more conditions
}
public void Dispense()
{
if (_state == "Dispensing")
{
_state = "NoMoney";
Console.WriteLine("Product dispensed");
}
// ... more conditions
}
}
This approach has problems:
- Complex conditional logic
- Hard to add new states
- State transitions scattered throughout code
- Violates Open/Closed Principle
- Difficult to test individual states
Solution
The State pattern allows an object to alter its behavior when its internal state changes. The object will appear to change its class.
Definition: The State pattern lets an object alter its behavior when its internal state changes. It appears as if the object changed its class.
Instead of using conditionals, each state is represented by a separate class. The context delegates state-specific behavior to the current state object.
Pseudocode
Context (VendingMachine)
↓ has reference to
State Interface (IVendingState)
↓ implemented by
Concrete States (NoMoneyState, HasMoneyState, DispensingState)
↓ each state can transition to
Other States
Examples
Here’s a complete, runnable C# implementation:
using System;
// State interface
public interface IVendingState
{
void InsertMoney(VendingMachine machine);
void SelectProduct(VendingMachine machine);
void Dispense(VendingMachine machine);
}
// Concrete states
public class NoMoneyState : IVendingState
{
public void InsertMoney(VendingMachine machine)
{
Console.WriteLine("Money inserted");
machine.SetState(new HasMoneyState());
}
public void SelectProduct(VendingMachine machine)
{
Console.WriteLine("Please insert money first");
}
public void Dispense(VendingMachine machine)
{
Console.WriteLine("Please insert money and select a product");
}
}
public class HasMoneyState : IVendingState
{
public void InsertMoney(VendingMachine machine)
{
Console.WriteLine("Money already inserted");
}
public void SelectProduct(VendingMachine machine)
{
Console.WriteLine("Product selected");
machine.SetState(new DispensingState());
}
public void Dispense(VendingMachine machine)
{
Console.WriteLine("Please select a product first");
}
}
public class DispensingState : IVendingState
{
public void InsertMoney(VendingMachine machine)
{
Console.WriteLine("Please wait, product is dispensing");
}
public void SelectProduct(VendingMachine machine)
{
Console.WriteLine("Please wait, product is dispensing");
}
public void Dispense(VendingMachine machine)
{
Console.WriteLine("Product dispensed");
machine.SetState(new NoMoneyState());
}
}
// Context
public class VendingMachine
{
private IVendingState _currentState;
public VendingMachine()
{
_currentState = new NoMoneyState();
}
public void SetState(IVendingState state)
{
_currentState = state;
}
public void InsertMoney()
{
_currentState.InsertMoney(this);
}
public void SelectProduct()
{
_currentState.SelectProduct(this);
}
public void Dispense()
{
_currentState.Dispense(this);
}
}
// Usage
class Program
{
static void Main()
{
var machine = new VendingMachine();
machine.SelectProduct(); // No money
machine.InsertMoney();
machine.InsertMoney(); // Already has money
machine.SelectProduct();
machine.Dispense();
machine.SelectProduct(); // Back to no money
}
}
Output:
Please insert money first
Money inserted
Money already inserted
Product selected
Product dispensed
Please insert money first
Real-World Example: Document States
// State interface
public interface IDocumentState
{
void Publish(Document document);
void Archive(Document document);
void Delete(Document document);
}
// Concrete states
public class DraftState : IDocumentState
{
public void Publish(Document document)
{
Console.WriteLine("Publishing document...");
document.SetState(new PublishedState());
}
public void Archive(Document document)
{
Console.WriteLine("Cannot archive draft document");
}
public void Delete(Document document)
{
Console.WriteLine("Deleting draft document");
document.SetState(new DeletedState());
}
}
public class PublishedState : IDocumentState
{
public void Publish(Document document)
{
Console.WriteLine("Document is already published");
}
public void Archive(Document document)
{
Console.WriteLine("Archiving published document");
document.SetState(new ArchivedState());
}
public void Delete(Document document)
{
Console.WriteLine("Cannot delete published document. Archive it first.");
}
}
public class ArchivedState : IDocumentState
{
public void Publish(Document document)
{
Console.WriteLine("Cannot publish archived document");
}
public void Archive(Document document)
{
Console.WriteLine("Document is already archived");
}
public void Delete(Document document)
{
Console.WriteLine("Deleting archived document");
document.SetState(new DeletedState());
}
}
public class DeletedState : IDocumentState
{
public void Publish(Document document)
{
Console.WriteLine("Cannot publish deleted document");
}
public void Archive(Document document)
{
Console.WriteLine("Cannot archive deleted document");
}
public void Delete(Document document)
{
Console.WriteLine("Document is already deleted");
}
}
// Context
public class Document
{
private IDocumentState _state;
public Document()
{
_state = new DraftState();
}
public void SetState(IDocumentState state)
{
_state = state;
}
public void Publish()
{
_state.Publish(this);
}
public void Archive()
{
_state.Archive(this);
}
public void Delete()
{
_state.Delete(this);
}
}
// Usage
var doc = new Document();
doc.Publish();
doc.Archive();
doc.Delete();
Player State Example
public interface IPlayerState
{
void Play(MediaPlayer player);
void Pause(MediaPlayer player);
void Stop(MediaPlayer player);
}
public class StoppedState : IPlayerState
{
public void Play(MediaPlayer player)
{
Console.WriteLine("Starting playback");
player.SetState(new PlayingState());
}
public void Pause(MediaPlayer player)
{
Console.WriteLine("Cannot pause - player is stopped");
}
public void Stop(MediaPlayer player)
{
Console.WriteLine("Player is already stopped");
}
}
public class PlayingState : IPlayerState
{
public void Play(MediaPlayer player)
{
Console.WriteLine("Already playing");
}
public void Pause(MediaPlayer player)
{
Console.WriteLine("Pausing playback");
player.SetState(new PausedState());
}
public void Stop(MediaPlayer player)
{
Console.WriteLine("Stopping playback");
player.SetState(new StoppedState());
}
}
public class PausedState : IPlayerState
{
public void Play(MediaPlayer player)
{
Console.WriteLine("Resuming playback");
player.SetState(new PlayingState());
}
public void Pause(MediaPlayer player)
{
Console.WriteLine("Already paused");
}
public void Stop(MediaPlayer player)
{
Console.WriteLine("Stopping playback");
player.SetState(new StoppedState());
}
}
public class MediaPlayer
{
private IPlayerState _state = new StoppedState();
public void SetState(IPlayerState state)
{
_state = state;
}
public void Play() => _state.Play(this);
public void Pause() => _state.Pause(this);
public void Stop() => _state.Stop(this);
}
Applications
Enterprise Use Cases:
- Workflow Engines: Managing workflow states and transitions
- Game Development: Character states, game states
- Order Processing: Order lifecycle (pending, processing, shipped, delivered)
- Document Management: Document states (draft, review, approved, published)
- Media Players: Playback states (stopped, playing, paused)
- Transaction Processing: Transaction states
- UI Components: Component states (enabled, disabled, loading)
- Network Protocols: Connection states
Benefits:
- Eliminates large conditional statements
- Makes state transitions explicit
- Makes it easy to add new states
- Follows Single Responsibility Principle—each state class has one responsibility
- Follows Open/Closed Principle—open for extension, closed for modification
- Makes state-specific behavior clear and organized
When to Use:
- An object’s behavior depends on its state
- You have many conditional statements that depend on object state
- State-specific behavior changes frequently
- You need to make state transitions explicit
- State-specific code is complex
State vs Strategy:
- State: Behavior changes based on internal state (state is part of object)
- Strategy: Behavior changes based on selected algorithm (strategy is chosen externally)
Key Insight: The State pattern encapsulates state-specific behavior in separate classes. Instead of using conditionals to handle different states, each state is a class that knows how to handle operations in that state and how to transition to other states. This makes state management explicit and maintainable.
