Overview
The State Pattern is a behavioral design pattern that allows an object to change its behavior when its internal state changes. It enables an object to appear as though its class has changed by delegating behavior to state objects.=
This pattern encapsulates state-specific behavior into separate classes, making it easy to add or modify states without altering the context or other states.
Key Participants
Context
Maintains a reference to the current state.
Allows state transitions by delegating behavior to the current state object.
State
Defines an interface for encapsulating the behavior associated with a particular state of the context.
ConcreteState
Implements the state-specific behavior for a particular state.
Implementation in Code
Example: Vending Machine
// State Interface
interface State {
void insertCoin();
void dispenseItem();
}
// ConcreteState: No Coin
class NoCoinState implements State {
private VendingMachine vendingMachine;
public NoCoinState(VendingMachine vendingMachine) {
this.vendingMachine = vendingMachine;
}
@Override
public void insertCoin() {
System.out.println("Coin inserted.");
vendingMachine.setState(vendingMachine.getHasCoinState());
}
@Override
public void dispenseItem() {
System.out.println("Please insert a coin first.");
}
}
// ConcreteState: Has Coin
class HasCoinState implements State {
private VendingMachine vendingMachine;
public HasCoinState(VendingMachine vendingMachine) {
this.vendingMachine = vendingMachine;
}
@Override
public void insertCoin() {
System.out.println("Coin already inserted.");
}
@Override
public void dispenseItem() {
System.out.println("Item dispensed.");
vendingMachine.setState(vendingMachine.getNoCoinState());
}
}
// Context
class VendingMachine {
private State noCoinState;
private State hasCoinState;
private State currentState;
public VendingMachine() {
noCoinState = new NoCoinState(this);
hasCoinState = new HasCoinState(this);
currentState = noCoinState; // Initial state
}
public void insertCoin() {
currentState.insertCoin();
}
public void dispenseItem() {
currentState.dispenseItem();
}
public void setState(State state) {
this.currentState = state;
}
public State getNoCoinState() {
return noCoinState;
}
public State getHasCoinState() {
return hasCoinState;
}
}
// Client
public class StatePatternDemo {
public static void main(String[] args) {
VendingMachine vendingMachine = new VendingMachine();
vendingMachine.dispenseItem(); // No coin inserted
vendingMachine.insertCoin(); // Insert coin
vendingMachine.dispenseItem(); // Dispense item
vendingMachine.dispenseItem(); // No coin inserted again
}
}
Key Methods
setState(State): Allows the context to transition between states.
handle(Context): Defines behavior based on the current state.
Advantages
Simplifies Complex Logic: Eliminates large conditionals or switch-case statements.
Encapsulation: State-specific logic is encapsulated in separate classes.
Ease of Extension: Adding new states or modifying existing ones does not require changes to other parts of the code.
Disadvantages
Increased Number of Classes: Each state requires a separate class, which can lead to class explosion.
Tight Coupling: States may need to reference the context to perform transitions, creating a dependency.
Real-World Examples
Traffic Lights: Each light (red, yellow, green) represents a state, and transitions occur based on timers or triggers.
Document Workflow: A document might be in states like Draft, Review, Published, or Archived.
Media Player: States like Playing, Paused, and Stopped determine the behavior of the player.
When to Use the State Pattern?
When an object needs to change its behavior based on its state.
When state-specific behavior needs to be isolated into separate classes.
When adding new states should not require changing existing logic.