Home/Design Patterns/State Pattern

State Pattern

Explain Like I'm 5...

Imagine a TRAFFIC LIGHT at a busy intersection! The light changes colors, and each color makes the traffic light behave differently.

The Problem:

  • 🔴 Red light: Cars must STOP
  • 🟡 Yellow light: Cars should SLOW DOWN and prepare to stop
  • 🟢 Green light: Cars can GO
  • 💡 The traffic light changes its BEHAVIOR based on its current color (state)!

The Solution - State Pattern!

  • Each color is a different STATE
  • Each STATE knows what to do
  • When the light changes color, it changes STATE
  • The traffic light BEHAVES differently in each state automatically!

The Key Idea:

An object can change its BEHAVIOR when its internal state changes! It's like the traffic light appearing to be a completely different object when it turns from red to green - same light, totally different behavior!

Why Is This Pattern Useful?

  • No giant if-else or switch statements checking the current state!
  • Each state is its own class with its own behavior!
  • Easy to add new states without changing existing code!
  • The object appears to change its class when state changes!

Pattern Purpose

The State pattern allows an object to alter its behavior when its internal state changes. The object will appear to change its class. It encapsulates state-specific behavior and delegates requests to the current state object.

When to Use State Pattern

  • An object's behavior depends on its state, and it must change behavior at runtime
  • Operations have large, multipart conditional statements that depend on the object's state
  • State-specific behavior should be defined independently
  • You want to avoid duplicate code when several states have similar behavior

Components

Context:

Context: Maintains an instance of a ConcreteState subclass that defines the current state

State:

State: Defines an interface for encapsulating behavior associated with a particular state

ConcreteState:

ConcreteState: Each subclass implements behavior associated with a state of the Context

Java Implementations

Example 1: Vending Machine

A vending machine that changes behavior based on its current state (idle, has money, dispensing).

VendingMachineState.java
java
1
2
3
4
5
6
7
// State Interface
public interface VendingMachineState {
void insertMoney(VendingMachine machine);
void selectProduct(VendingMachine machine);
void dispense(VendingMachine machine);
void ejectMoney(VendingMachine machine);
}
IdleState.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Concrete State 1: Idle (waiting for money)
public class IdleState implements VendingMachineState {
@Override
public void insertMoney(VendingMachine machine) {
System.out.println("Money inserted. Please select a product.");
machine.setState(machine.getHasMoneyState());
}
@Override
public void selectProduct(VendingMachine machine) {
System.out.println("Please insert money first!");
}
@Override
public void dispense(VendingMachine machine) {
System.out.println("Please insert money and select product first!");
}
@Override
public void ejectMoney(VendingMachine machine) {
System.out.println("No money to eject!");
}
}
HasMoneyState.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Concrete State 2: Has Money (ready to select product)
public class HasMoneyState implements VendingMachineState {
@Override
public void insertMoney(VendingMachine machine) {
System.out.println("Money already inserted. Please select a product.");
}
@Override
public void selectProduct(VendingMachine machine) {
System.out.println("Product selected. Dispensing...");
machine.setState(machine.getDispensingState());
machine.dispense();
}
@Override
public void dispense(VendingMachine machine) {
System.out.println("Please select a product first!");
}
@Override
public void ejectMoney(VendingMachine machine) {
System.out.println("Money ejected. Returning to idle state.");
machine.setState(machine.getIdleState());
}
}
DispensingState.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// Concrete State 3: Dispensing (giving product)
public class DispensingState implements VendingMachineState {
@Override
public void insertMoney(VendingMachine machine) {
System.out.println("Please wait, already dispensing product.");
}
@Override
public void selectProduct(VendingMachine machine) {
System.out.println("Please wait, already dispensing product.");
}
@Override
public void dispense(VendingMachine machine) {
System.out.println("Product dispensed! Enjoy!");
if (machine.getProductCount() > 0) {
machine.decrementProductCount();
machine.setState(machine.getIdleState());
} else {
System.out.println("Out of stock!");
machine.setState(machine.getIdleState());
}
}
@Override
public void ejectMoney(VendingMachine machine) {
System.out.println("Too late! Product is being dispensed.");
}
}
VendingMachine.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
// Context: The Vending Machine
public class VendingMachine {
private VendingMachineState idleState;
private VendingMachineState hasMoneyState;
private VendingMachineState dispensingState;
private VendingMachineState currentState;
private int productCount;
public VendingMachine(int productCount) {
this.productCount = productCount;
// Create all possible states
idleState = new IdleState();
hasMoneyState = new HasMoneyState();
dispensingState = new DispensingState();
// Start in idle state
currentState = idleState;
}
// Delegate all operations to current state
public void insertMoney() {
currentState.insertMoney(this);
}
public void selectProduct() {
currentState.selectProduct(this);
}
public void dispense() {
currentState.dispense(this);
}
public void ejectMoney() {
currentState.ejectMoney(this);
}
// State management
public void setState(VendingMachineState state) {
this.currentState = state;
}
// Getters for states
public VendingMachineState getIdleState() {
return idleState;
}
public VendingMachineState getHasMoneyState() {
return hasMoneyState;
}
public VendingMachineState getDispensingState() {
return dispensingState;
}
// Product management
public int getProductCount() {
return productCount;
}
public void decrementProductCount() {
if (productCount > 0) {
productCount--;
}
}
public static void main(String[] args) {
VendingMachine machine = new VendingMachine(3);
System.out.println("=== Scenario 1: Normal Purchase ===");
machine.insertMoney();
machine.selectProduct();
System.out.println("\n=== Scenario 2: Try to select without money ===");
machine.selectProduct();
System.out.println("\n=== Scenario 3: Eject money ===");
machine.insertMoney();
machine.ejectMoney();
System.out.println("\n=== Scenario 4: Double insert ===");
machine.insertMoney();
machine.insertMoney();
machine.selectProduct();
}
}

Example 2: Phone Call

A phone that behaves differently when ringing, connected, or on hold.

PhoneState.java
java
1
2
3
4
5
6
7
// State Interface
public interface PhoneState {
void dial(Phone phone, String number);
void answer(Phone phone);
void hold(Phone phone);
void hangUp(Phone phone);
}
RingingState.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Concrete State: Phone is Ringing
public class RingingState implements PhoneState {
@Override
public void dial(Phone phone, String number) {
System.out.println("Already in a call! Cannot dial.");
}
@Override
public void answer(Phone phone) {
System.out.println("Call answered! You are now connected.");
phone.setState(phone.getConnectedState());
}
@Override
public void hold(Phone phone) {
System.out.println("Cannot hold a ringing call! Answer first.");
}
@Override
public void hangUp(Phone phone) {
System.out.println("Call rejected. Back to idle.");
phone.setState(phone.getIdleState());
}
}
ConnectedState.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Concrete State: Call is Connected
public class ConnectedState implements PhoneState {
@Override
public void dial(Phone phone, String number) {
System.out.println("Already connected to a call!");
}
@Override
public void answer(Phone phone) {
System.out.println("Already connected!");
}
@Override
public void hold(Phone phone) {
System.out.println("Call placed on hold.");
phone.setState(phone.getOnHoldState());
}
@Override
public void hangUp(Phone phone) {
System.out.println("Call ended. Back to idle.");
phone.setState(phone.getIdleState());
}
}
OnHoldState.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Concrete State: Call is On Hold
public class OnHoldState implements PhoneState {
@Override
public void dial(Phone phone, String number) {
System.out.println("Cannot dial while call is on hold!");
}
@Override
public void answer(Phone phone) {
System.out.println("Resuming call from hold.");
phone.setState(phone.getConnectedState());
}
@Override
public void hold(Phone phone) {
System.out.println("Call is already on hold.");
}
@Override
public void hangUp(Phone phone) {
System.out.println("Ending held call. Back to idle.");
phone.setState(phone.getIdleState());
}
}
IdleState.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Concrete State: Phone is Idle
public class IdleState implements PhoneState {
@Override
public void dial(Phone phone, String number) {
System.out.println("Dialing " + number + "...");
System.out.println("Phone is ringing...");
phone.setState(phone.getRingingState());
}
@Override
public void answer(Phone phone) {
System.out.println("No incoming call to answer.");
}
@Override
public void hold(Phone phone) {
System.out.println("No active call to hold.");
}
@Override
public void hangUp(Phone phone) {
System.out.println("No call to hang up.");
}
}
Phone.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
// Context: The Phone
public class Phone {
private PhoneState idleState;
private PhoneState ringingState;
private PhoneState connectedState;
private PhoneState onHoldState;
private PhoneState currentState;
public Phone() {
// Initialize all states
idleState = new IdleState();
ringingState = new RingingState();
connectedState = new ConnectedState();
onHoldState = new OnHoldState();
// Start idle
currentState = idleState;
}
// Delegate to current state
public void dial(String number) {
currentState.dial(this, number);
}
public void answer() {
currentState.answer(this);
}
public void hold() {
currentState.hold(this);
}
public void hangUp() {
currentState.hangUp(this);
}
// State management
public void setState(PhoneState state) {
this.currentState = state;
}
// State getters
public PhoneState getIdleState() {
return idleState;
}
public PhoneState getRingingState() {
return ringingState;
}
public PhoneState getConnectedState() {
return connectedState;
}
public PhoneState getOnHoldState() {
return onHoldState;
}
public static void main(String[] args) {
Phone phone = new Phone();
System.out.println("=== Making a call ===");
phone.dial("555-1234");
phone.answer();
System.out.println("\n=== Putting call on hold ===");
phone.hold();
System.out.println("\n=== Resuming call ===");
phone.answer();
System.out.println("\n=== Ending call ===");
phone.hangUp();
}
}

Example 3: Document Workflow

A document that goes through different states: draft, moderation, published.

DocumentState.java
java
1
2
3
4
5
6
7
8
// State Interface
public interface DocumentState {
void edit(Document document, String content);
void submit(Document document);
void approve(Document document);
void reject(Document document);
void publish(Document document);
}
DraftState.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// Concrete State: Document in Draft
public class DraftState implements DocumentState {
@Override
public void edit(Document document, String content) {
System.out.println("Editing draft: " + content);
document.setContent(content);
}
@Override
public void submit(Document document) {
System.out.println("Submitting draft for moderation...");
document.setState(document.getModerationState());
}
@Override
public void approve(Document document) {
System.out.println("Cannot approve a draft. Submit it first!");
}
@Override
public void reject(Document document) {
System.out.println("Cannot reject a draft.");
}
@Override
public void publish(Document document) {
System.out.println("Cannot publish a draft. Submit and approve first!");
}
}
ModerationState.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// Concrete State: Document in Moderation
public class ModerationState implements DocumentState {
@Override
public void edit(Document document, String content) {
System.out.println("Cannot edit while in moderation!");
}
@Override
public void submit(Document document) {
System.out.println("Document is already in moderation.");
}
@Override
public void approve(Document document) {
System.out.println("Document approved! Publishing...");
document.setState(document.getPublishedState());
}
@Override
public void reject(Document document) {
System.out.println("Document rejected. Sending back to draft.");
document.setState(document.getDraftState());
}
@Override
public void publish(Document document) {
System.out.println("Document must be approved before publishing!");
}
}
PublishedState.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Concrete State: Document Published
public class PublishedState implements DocumentState {
@Override
public void edit(Document document, String content) {
System.out.println("Cannot edit a published document directly!");
System.out.println("Create a new draft or unpublish first.");
}
@Override
public void submit(Document document) {
System.out.println("Document is already published.");
}
@Override
public void approve(Document document) {
System.out.println("Document is already published.");
}
@Override
public void reject(Document document) {
System.out.println("Cannot reject a published document.");
}
@Override
public void publish(Document document) {
System.out.println("Document is already published!");
}
}
Document.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
// Context: The Document
public class Document {
private DocumentState draftState;
private DocumentState moderationState;
private DocumentState publishedState;
private DocumentState currentState;
private String content;
private String author;
public Document(String author) {
this.author = author;
this.content = "";
// Initialize all states
draftState = new DraftState();
moderationState = new ModerationState();
publishedState = new PublishedState();
// Start in draft state
currentState = draftState;
}
// Delegate to current state
public void edit(String content) {
currentState.edit(this, content);
}
public void submit() {
currentState.submit(this);
}
public void approve() {
currentState.approve(this);
}
public void reject() {
currentState.reject(this);
}
public void publish() {
currentState.publish(this);
}
// State management
public void setState(DocumentState state) {
this.currentState = state;
}
// State getters
public DocumentState getDraftState() {
return draftState;
}
public DocumentState getModerationState() {
return moderationState;
}
public DocumentState getPublishedState() {
return publishedState;
}
// Content management
public void setContent(String content) {
this.content = content;
}
public String getContent() {
return content;
}
public String getAuthor() {
return author;
}
public static void main(String[] args) {
Document doc = new Document("John Doe");
System.out.println("=== Writing and submitting document ===");
doc.edit("Introduction to State Pattern");
doc.edit("The State Pattern is a behavioral design pattern...");
doc.submit();
System.out.println("\n=== Try to edit during moderation ===");
doc.edit("Cannot edit now!");
System.out.println("\n=== Approve and publish ===");
doc.approve();
System.out.println("\n=== Try to edit published document ===");
doc.edit("Cannot edit published!");
System.out.println("\n=== New document that gets rejected ===");
Document doc2 = new Document("Jane Smith");
doc2.edit("Poor quality content");
doc2.submit();
doc2.reject();
doc2.edit("Improved content after feedback");
doc2.submit();
doc2.approve();
}
}

Real-World Examples

  • 🔌TCP Connection: Different states like Established, Listen, Closed
  • 📦Order Processing: Pending, Processing, Shipped, Delivered
  • 🎮Game Characters: Different states like Idle, Walking, Running, Jumping
  • ▶️Media Player: Playing, Paused, Stopped states
  • 🛗Elevator: Moving Up, Moving Down, Stopped, Door Open states

Benefits

  • Single Responsibility: State-specific behavior is in separate classes
  • Open/Closed Principle: Add new states without changing existing state classes or context
  • Simplifies Code: Eliminates bulky state machine conditionals
  • State Transitions: State objects can control when state transitions occur

Drawbacks

  • ⚠️Can be overkill for simple state machines with few states
  • ⚠️Increases number of classes in the system
  • ⚠️State classes may need access to context's private data

Key Points to Remember

  • 1️⃣State pattern allows objects to alter behavior when internal state changes
  • 2️⃣Each state is encapsulated in a separate class
  • 3️⃣Context delegates state-specific requests to current state object
  • 4️⃣Different from Strategy - State manages its own transitions, Strategy is chosen by client
  • 5️⃣States can be shared if they don't have instance variables (Flyweight pattern)

Practice Scenarios

  • Create a traffic light system with Red, Yellow, Green states and automatic transitions
  • Build a music player with Play, Pause, Stop, Fast Forward states
  • Implement an ATM machine with different states (No Card, Has Card, Wrong PIN, Transaction)
  • Design a garage door controller with states (Open, Closed, Opening, Closing, Stopped)
  • Create a booking system with states (Available, Reserved, Booked, Cancelled)