State Design Pattern in Java
1. Introduction
The State Pattern allows an object to change its behavior when its internal state changes.
It appears as if the object has changed its class dynamically, but actually, it’s just switching between states.
2. Real-World Analogy
Think of an ATM Machine:
- If you insert a card but there’s no money → ATM shows “Out of Service”.
- If ATM has money and card is inserted → ATM shows “Enter PIN”.
- Based on the state (NoCard, HasCard, Authenticated, OutOfMoney), the ATM behaves differently.
3. Structure of State Pattern
- State (Interface) → Defines behavior that changes with the state.
- Concrete States → Implement specific behaviors.
- Context (Main Object) → Maintains a reference to the current state and delegates behavior to it.
4. Custom Implementation Example
Let’s implement an ATM Machine State System.
Step 1 – State Interface
public interface ATMState {
void insertCard();
void enterPin(int pin);
void withdrawCash(int amount);
}
Step 2 – Concrete States
NoCard State
public class NoCardState implements ATMState {
private ATMMachine atm;
public NoCardState(ATMMachine atm) {
this.atm = atm;
}
@Override
public void insertCard() {
System.out.println("Card inserted.");
atm.setState(atm.getHasCardState());
}
@Override
public void enterPin(int pin) {
System.out.println("No card inserted.");
}
@Override
public void withdrawCash(int amount) {
System.out.println("No card inserted.");
}
}
HasCard State
public class HasCardState implements ATMState {
private ATMMachine atm;
public HasCardState(ATMMachine atm) {
this.atm = atm;
}
@Override
public void insertCard() {
System.out.println("Card already inserted.");
}
@Override
public void enterPin(int pin) {
if (pin == 1234) {
System.out.println("PIN correct.");
atm.setState(atm.getAuthenticatedState());
} else {
System.out.println("Wrong PIN.");
atm.setState(atm.getNoCardState());
}
}
@Override
public void withdrawCash(int amount) {
System.out.println("Enter PIN first.");
}
}
Authenticated State
public class AuthenticatedState implements ATMState {
private ATMMachine atm;
public AuthenticatedState(ATMMachine atm) {
this.atm = atm;
}
@Override
public void insertCard() {
System.out.println("Card already inserted.");
}
@Override
public void enterPin(int pin) {
System.out.println("PIN already entered.");
}
@Override
public void withdrawCash(int amount) {
if (atm.getCash() >= amount) {
System.out.println("Dispensed: $" + amount);
atm.setCash(atm.getCash() - amount);
if (atm.getCash() <= 0) {
atm.setState(atm.getOutOfMoneyState());
}
} else {
System.out.println("Insufficient funds in ATM.");
atm.setState(atm.getOutOfMoneyState());
}
}
}
OutOfMoney State
public class OutOfMoneyState implements ATMState {
private ATMMachine atm;
public OutOfMoneyState(ATMMachine atm) {
this.atm = atm;
}
@Override
public void insertCard() {
System.out.println("ATM out of money.");
}
@Override
public void enterPin(int pin) {
System.out.println("ATM out of money.");
}
@Override
public void withdrawCash(int amount) {
System.out.println("ATM out of money.");
}
}
Step 3 – Context (ATM Machine)
public class ATMMachine {
private ATMState noCardState;
private ATMState hasCardState;
private ATMState authenticatedState;
private ATMState outOfMoneyState;
private ATMState currentState;
private int cash;
public ATMMachine(int initialCash) {
noCardState = new NoCardState(this);
hasCardState = new HasCardState(this);
authenticatedState = new AuthenticatedState(this);
outOfMoneyState = new OutOfMoneyState(this);
this.cash = initialCash;
currentState = (initialCash > 0) ? noCardState : outOfMoneyState;
}
public void setState(ATMState state) {
currentState = state;
}
public void setCash(int cash) {
this.cash = cash;
}
public int getCash() {
return cash;
}
public ATMState getNoCardState() { return noCardState; }
public ATMState getHasCardState() { return hasCardState; }
public ATMState getAuthenticatedState() { return authenticatedState; }
public ATMState getOutOfMoneyState() { return outOfMoneyState; }
// Delegation
public void insertCard() { currentState.insertCard(); }
public void enterPin(int pin) { currentState.enterPin(pin); }
public void withdrawCash(int amount) { currentState.withdrawCash(amount); }
}
Step 4 – Client Code
public class StatePatternDemo {
public static void main(String[] args) {
ATMMachine atm = new ATMMachine(100);
atm.insertCard();
atm.enterPin(1234);
atm.withdrawCash(50);
atm.insertCard();
atm.enterPin(1234);
atm.withdrawCash(60); // ATM out of money now
}
}
5. Output
Card inserted.
PIN correct.
Dispensed: $50
Card inserted.
PIN correct.
Insufficient funds in ATM.
6. Advantages
- Cleaner code by removing multiple if-else conditions.
- Makes it easier to add new states.
- State transitions are encapsulated within state classes.
7. Real-World Use Cases
- ATM machines.
- Media players (Play, Pause, Stop).
- Traffic lights.
- Document workflow systems (Draft, Review, Published).
Next- Strategy Design Pattern in Java