Design Patterns in Java (5 Part Series)
1 Strategy Design Pattern in Java
2 Template Method Pattern in Java
3 Factory Method in Java
4 Bridge Pattern in Java
5 State Design Pattern in Java
The State Design Pattern allows an object to change its behavior when its internal state changes. This pattern is particularly useful when an object must behave differently depending on its state, without requiring modifications to the code that interacts with it.
When to Use the State Pattern?
The State Pattern is ideal when:
- An object has multiple states, each with different behaviors.
- Avoiding complex conditional statements (
if-else
orswitch-case
) to handle state transitions. - You want to encapsulate state-specific behavior in separate classes for better maintainability.
Implementing the State Pattern in Java
Let’s look at a simple implementation using a vending machine that can be in different states: NoCredit
, WithCredit
, and DispensingBeverage
.
1. State Interface
The interface defines behaviors that vary based on the machine’s state.
<span>public</span> <span>interface</span> <span>State</span> <span>{</span><span>void</span> <span>insertCredit</span><span>();</span><span>void</span> <span>ejectCredit</span><span>();</span><span>void</span> <span>dispenseBeverage</span><span>();</span><span>}</span><span>public</span> <span>interface</span> <span>State</span> <span>{</span> <span>void</span> <span>insertCredit</span><span>();</span> <span>void</span> <span>ejectCredit</span><span>();</span> <span>void</span> <span>dispenseBeverage</span><span>();</span> <span>}</span>public interface State { void insertCredit(); void ejectCredit(); void dispenseBeverage(); }
Enter fullscreen mode Exit fullscreen mode
2. Context Class (Vending Machine)
This class maintains the current state and dynamically updates it as actions are performed.
<span>public</span> <span>class</span> <span>VendingMachine</span> <span>{</span><span>private</span> <span>State</span> <span>currentState</span><span>;</span><span>private</span> <span>final</span> <span>State</span> <span>noCreditState</span> <span>=</span> <span>new</span> <span>NoCredit</span><span>(</span><span>this</span><span>);</span><span>private</span> <span>final</span> <span>State</span> <span>withCreditState</span> <span>=</span> <span>new</span> <span>WithCredit</span><span>(</span><span>this</span><span>);</span><span>private</span> <span>final</span> <span>State</span> <span>dispensingState</span> <span>=</span> <span>new</span> <span>DispensingBeverage</span><span>(</span><span>this</span><span>);</span><span>public</span> <span>VendingMachine</span><span>()</span> <span>{</span><span>this</span><span>.</span><span>currentState</span> <span>=</span> <span>noCreditState</span><span>;</span><span>}</span><span>public</span> <span>void</span> <span>setState</span><span>(</span><span>State</span> <span>newState</span><span>)</span> <span>{</span><span>this</span><span>.</span><span>currentState</span> <span>=</span> <span>newState</span><span>;</span><span>}</span><span>public</span> <span>State</span> <span>getNoCreditState</span><span>()</span> <span>{</span> <span>return</span> <span>noCreditState</span><span>;</span> <span>}</span><span>public</span> <span>State</span> <span>getWithCreditState</span><span>()</span> <span>{</span> <span>return</span> <span>withCreditState</span><span>;</span> <span>}</span><span>public</span> <span>State</span> <span>getDispensingState</span><span>()</span> <span>{</span> <span>return</span> <span>dispensingState</span><span>;</span> <span>}</span><span>public</span> <span>void</span> <span>insertCredit</span><span>()</span> <span>{</span> <span>currentState</span><span>.</span><span>insertCredit</span><span>();</span> <span>}</span><span>public</span> <span>void</span> <span>ejectCredit</span><span>()</span> <span>{</span> <span>currentState</span><span>.</span><span>ejectCredit</span><span>();</span> <span>}</span><span>public</span> <span>void</span> <span>dispenseBeverage</span><span>()</span> <span>{</span> <span>currentState</span><span>.</span><span>dispenseBeverage</span><span>();</span> <span>}</span><span>}</span><span>public</span> <span>class</span> <span>VendingMachine</span> <span>{</span> <span>private</span> <span>State</span> <span>currentState</span><span>;</span> <span>private</span> <span>final</span> <span>State</span> <span>noCreditState</span> <span>=</span> <span>new</span> <span>NoCredit</span><span>(</span><span>this</span><span>);</span> <span>private</span> <span>final</span> <span>State</span> <span>withCreditState</span> <span>=</span> <span>new</span> <span>WithCredit</span><span>(</span><span>this</span><span>);</span> <span>private</span> <span>final</span> <span>State</span> <span>dispensingState</span> <span>=</span> <span>new</span> <span>DispensingBeverage</span><span>(</span><span>this</span><span>);</span> <span>public</span> <span>VendingMachine</span><span>()</span> <span>{</span> <span>this</span><span>.</span><span>currentState</span> <span>=</span> <span>noCreditState</span><span>;</span> <span>}</span> <span>public</span> <span>void</span> <span>setState</span><span>(</span><span>State</span> <span>newState</span><span>)</span> <span>{</span> <span>this</span><span>.</span><span>currentState</span> <span>=</span> <span>newState</span><span>;</span> <span>}</span> <span>public</span> <span>State</span> <span>getNoCreditState</span><span>()</span> <span>{</span> <span>return</span> <span>noCreditState</span><span>;</span> <span>}</span> <span>public</span> <span>State</span> <span>getWithCreditState</span><span>()</span> <span>{</span> <span>return</span> <span>withCreditState</span><span>;</span> <span>}</span> <span>public</span> <span>State</span> <span>getDispensingState</span><span>()</span> <span>{</span> <span>return</span> <span>dispensingState</span><span>;</span> <span>}</span> <span>public</span> <span>void</span> <span>insertCredit</span><span>()</span> <span>{</span> <span>currentState</span><span>.</span><span>insertCredit</span><span>();</span> <span>}</span> <span>public</span> <span>void</span> <span>ejectCredit</span><span>()</span> <span>{</span> <span>currentState</span><span>.</span><span>ejectCredit</span><span>();</span> <span>}</span> <span>public</span> <span>void</span> <span>dispenseBeverage</span><span>()</span> <span>{</span> <span>currentState</span><span>.</span><span>dispenseBeverage</span><span>();</span> <span>}</span> <span>}</span>public class VendingMachine { private State currentState; private final State noCreditState = new NoCredit(this); private final State withCreditState = new WithCredit(this); private final State dispensingState = new DispensingBeverage(this); public VendingMachine() { this.currentState = noCreditState; } public void setState(State newState) { this.currentState = newState; } public State getNoCreditState() { return noCreditState; } public State getWithCreditState() { return withCreditState; } public State getDispensingState() { return dispensingState; } public void insertCredit() { currentState.insertCredit(); } public void ejectCredit() { currentState.ejectCredit(); } public void dispenseBeverage() { currentState.dispenseBeverage(); } }
Enter fullscreen mode Exit fullscreen mode
3. State Implementations
Each state class implements the State
interface and modifies the machine’s state when appropriate.
<span>// State: No Credit</span><span>class</span> <span>NoCredit</span> <span>implements</span> <span>State</span> <span>{</span><span>private</span> <span>VendingMachine</span> <span>machine</span><span>;</span><span>public</span> <span>NoCredit</span><span>(</span><span>VendingMachine</span> <span>machine</span><span>)</span> <span>{</span> <span>this</span><span>.</span><span>machine</span> <span>=</span> <span>machine</span><span>;</span> <span>}</span><span>@Override</span><span>public</span> <span>void</span> <span>insertCredit</span><span>()</span> <span>{</span><span>System</span><span>.</span><span>out</span><span>.</span><span>println</span><span>(</span><span>"Credit inserted. You can now buy a beverage."</span><span>);</span><span>machine</span><span>.</span><span>setState</span><span>(</span><span>machine</span><span>.</span><span>getWithCreditState</span><span>());</span><span>}</span><span>@Override</span><span>public</span> <span>void</span> <span>ejectCredit</span><span>()</span> <span>{</span> <span>System</span><span>.</span><span>out</span><span>.</span><span>println</span><span>(</span><span>"No credit to eject."</span><span>);</span> <span>}</span><span>@Override</span><span>public</span> <span>void</span> <span>dispenseBeverage</span><span>()</span> <span>{</span> <span>System</span><span>.</span><span>out</span><span>.</span><span>println</span><span>(</span><span>"No credit. Cannot dispense beverage."</span><span>);</span> <span>}</span><span>}</span><span>// State: With Credit</span><span>class</span> <span>WithCredit</span> <span>implements</span> <span>State</span> <span>{</span><span>private</span> <span>VendingMachine</span> <span>machine</span><span>;</span><span>public</span> <span>WithCredit</span><span>(</span><span>VendingMachine</span> <span>machine</span><span>)</span> <span>{</span> <span>this</span><span>.</span><span>machine</span> <span>=</span> <span>machine</span><span>;</span> <span>}</span><span>@Override</span><span>public</span> <span>void</span> <span>insertCredit</span><span>()</span> <span>{</span> <span>System</span><span>.</span><span>out</span><span>.</span><span>println</span><span>(</span><span>"Credit already inserted."</span><span>);</span> <span>}</span><span>@Override</span><span>public</span> <span>void</span> <span>ejectCredit</span><span>()</span> <span>{</span><span>System</span><span>.</span><span>out</span><span>.</span><span>println</span><span>(</span><span>"Credit ejected. The machine now has no credit."</span><span>);</span><span>machine</span><span>.</span><span>setState</span><span>(</span><span>machine</span><span>.</span><span>getNoCreditState</span><span>());</span><span>}</span><span>@Override</span><span>public</span> <span>void</span> <span>dispenseBeverage</span><span>()</span> <span>{</span><span>System</span><span>.</span><span>out</span><span>.</span><span>println</span><span>(</span><span>"Beverage dispensed. Enjoy!"</span><span>);</span><span>machine</span><span>.</span><span>setState</span><span>(</span><span>machine</span><span>.</span><span>getDispensingState</span><span>());</span><span>}</span><span>}</span><span>// State: Dispensing Beverage</span><span>class</span> <span>DispensingBeverage</span> <span>implements</span> <span>State</span> <span>{</span><span>private</span> <span>VendingMachine</span> <span>machine</span><span>;</span><span>public</span> <span>DispensingBeverage</span><span>(</span><span>VendingMachine</span> <span>machine</span><span>)</span> <span>{</span> <span>this</span><span>.</span><span>machine</span> <span>=</span> <span>machine</span><span>;</span> <span>}</span><span>@Override</span><span>public</span> <span>void</span> <span>insertCredit</span><span>()</span> <span>{</span> <span>System</span><span>.</span><span>out</span><span>.</span><span>println</span><span>(</span><span>"The machine is dispensing. Please wait."</span><span>);</span> <span>}</span><span>@Override</span><span>public</span> <span>void</span> <span>ejectCredit</span><span>()</span> <span>{</span> <span>System</span><span>.</span><span>out</span><span>.</span><span>println</span><span>(</span><span>"Cannot eject credit while dispensing."</span><span>);</span> <span>}</span><span>@Override</span><span>public</span> <span>void</span> <span>dispenseBeverage</span><span>()</span> <span>{</span><span>System</span><span>.</span><span>out</span><span>.</span><span>println</span><span>(</span><span>"Beverage already dispensed. Waiting for next transaction."</span><span>);</span><span>machine</span><span>.</span><span>setState</span><span>(</span><span>machine</span><span>.</span><span>getNoCreditState</span><span>());</span><span>}</span><span>}</span><span>// State: No Credit</span> <span>class</span> <span>NoCredit</span> <span>implements</span> <span>State</span> <span>{</span> <span>private</span> <span>VendingMachine</span> <span>machine</span><span>;</span> <span>public</span> <span>NoCredit</span><span>(</span><span>VendingMachine</span> <span>machine</span><span>)</span> <span>{</span> <span>this</span><span>.</span><span>machine</span> <span>=</span> <span>machine</span><span>;</span> <span>}</span> <span>@Override</span> <span>public</span> <span>void</span> <span>insertCredit</span><span>()</span> <span>{</span> <span>System</span><span>.</span><span>out</span><span>.</span><span>println</span><span>(</span><span>"Credit inserted. You can now buy a beverage."</span><span>);</span> <span>machine</span><span>.</span><span>setState</span><span>(</span><span>machine</span><span>.</span><span>getWithCreditState</span><span>());</span> <span>}</span> <span>@Override</span> <span>public</span> <span>void</span> <span>ejectCredit</span><span>()</span> <span>{</span> <span>System</span><span>.</span><span>out</span><span>.</span><span>println</span><span>(</span><span>"No credit to eject."</span><span>);</span> <span>}</span> <span>@Override</span> <span>public</span> <span>void</span> <span>dispenseBeverage</span><span>()</span> <span>{</span> <span>System</span><span>.</span><span>out</span><span>.</span><span>println</span><span>(</span><span>"No credit. Cannot dispense beverage."</span><span>);</span> <span>}</span> <span>}</span> <span>// State: With Credit</span> <span>class</span> <span>WithCredit</span> <span>implements</span> <span>State</span> <span>{</span> <span>private</span> <span>VendingMachine</span> <span>machine</span><span>;</span> <span>public</span> <span>WithCredit</span><span>(</span><span>VendingMachine</span> <span>machine</span><span>)</span> <span>{</span> <span>this</span><span>.</span><span>machine</span> <span>=</span> <span>machine</span><span>;</span> <span>}</span> <span>@Override</span> <span>public</span> <span>void</span> <span>insertCredit</span><span>()</span> <span>{</span> <span>System</span><span>.</span><span>out</span><span>.</span><span>println</span><span>(</span><span>"Credit already inserted."</span><span>);</span> <span>}</span> <span>@Override</span> <span>public</span> <span>void</span> <span>ejectCredit</span><span>()</span> <span>{</span> <span>System</span><span>.</span><span>out</span><span>.</span><span>println</span><span>(</span><span>"Credit ejected. The machine now has no credit."</span><span>);</span> <span>machine</span><span>.</span><span>setState</span><span>(</span><span>machine</span><span>.</span><span>getNoCreditState</span><span>());</span> <span>}</span> <span>@Override</span> <span>public</span> <span>void</span> <span>dispenseBeverage</span><span>()</span> <span>{</span> <span>System</span><span>.</span><span>out</span><span>.</span><span>println</span><span>(</span><span>"Beverage dispensed. Enjoy!"</span><span>);</span> <span>machine</span><span>.</span><span>setState</span><span>(</span><span>machine</span><span>.</span><span>getDispensingState</span><span>());</span> <span>}</span> <span>}</span> <span>// State: Dispensing Beverage</span> <span>class</span> <span>DispensingBeverage</span> <span>implements</span> <span>State</span> <span>{</span> <span>private</span> <span>VendingMachine</span> <span>machine</span><span>;</span> <span>public</span> <span>DispensingBeverage</span><span>(</span><span>VendingMachine</span> <span>machine</span><span>)</span> <span>{</span> <span>this</span><span>.</span><span>machine</span> <span>=</span> <span>machine</span><span>;</span> <span>}</span> <span>@Override</span> <span>public</span> <span>void</span> <span>insertCredit</span><span>()</span> <span>{</span> <span>System</span><span>.</span><span>out</span><span>.</span><span>println</span><span>(</span><span>"The machine is dispensing. Please wait."</span><span>);</span> <span>}</span> <span>@Override</span> <span>public</span> <span>void</span> <span>ejectCredit</span><span>()</span> <span>{</span> <span>System</span><span>.</span><span>out</span><span>.</span><span>println</span><span>(</span><span>"Cannot eject credit while dispensing."</span><span>);</span> <span>}</span> <span>@Override</span> <span>public</span> <span>void</span> <span>dispenseBeverage</span><span>()</span> <span>{</span> <span>System</span><span>.</span><span>out</span><span>.</span><span>println</span><span>(</span><span>"Beverage already dispensed. Waiting for next transaction."</span><span>);</span> <span>machine</span><span>.</span><span>setState</span><span>(</span><span>machine</span><span>.</span><span>getNoCreditState</span><span>());</span> <span>}</span> <span>}</span>// State: No Credit class NoCredit implements State { private VendingMachine machine; public NoCredit(VendingMachine machine) { this.machine = machine; } @Override public void insertCredit() { System.out.println("Credit inserted. You can now buy a beverage."); machine.setState(machine.getWithCreditState()); } @Override public void ejectCredit() { System.out.println("No credit to eject."); } @Override public void dispenseBeverage() { System.out.println("No credit. Cannot dispense beverage."); } } // State: With Credit class WithCredit implements State { private VendingMachine machine; public WithCredit(VendingMachine machine) { this.machine = machine; } @Override public void insertCredit() { System.out.println("Credit already inserted."); } @Override public void ejectCredit() { System.out.println("Credit ejected. The machine now has no credit."); machine.setState(machine.getNoCreditState()); } @Override public void dispenseBeverage() { System.out.println("Beverage dispensed. Enjoy!"); machine.setState(machine.getDispensingState()); } } // State: Dispensing Beverage class DispensingBeverage implements State { private VendingMachine machine; public DispensingBeverage(VendingMachine machine) { this.machine = machine; } @Override public void insertCredit() { System.out.println("The machine is dispensing. Please wait."); } @Override public void ejectCredit() { System.out.println("Cannot eject credit while dispensing."); } @Override public void dispenseBeverage() { System.out.println("Beverage already dispensed. Waiting for next transaction."); machine.setState(machine.getNoCreditState()); } }
Enter fullscreen mode Exit fullscreen mode
4. Testing the Vending Machine
With the improved design, the state transitions happen automatically within the state classes.
<span>public</span> <span>class</span> <span>VendingMachineTest</span> <span>{</span><span>public</span> <span>static</span> <span>void</span> <span>main</span><span>(</span><span>String</span><span>[]</span> <span>args</span><span>)</span> <span>{</span><span>VendingMachine</span> <span>machine</span> <span>=</span> <span>new</span> <span>VendingMachine</span><span>();</span><span>machine</span><span>.</span><span>insertCredit</span><span>();</span> <span>// Insert credit → Changes to "With Credit"</span><span>machine</span><span>.</span><span>dispenseBeverage</span><span>();</span> <span>// Dispense beverage → Changes to "Dispensing"</span><span>machine</span><span>.</span><span>dispenseBeverage</span><span>();</span> <span>// Complete transaction → Returns to "No Credit"</span><span>machine</span><span>.</span><span>ejectCredit</span><span>();</span> <span>// No credit to eject</span><span>}</span><span>}</span><span>public</span> <span>class</span> <span>VendingMachineTest</span> <span>{</span> <span>public</span> <span>static</span> <span>void</span> <span>main</span><span>(</span><span>String</span><span>[]</span> <span>args</span><span>)</span> <span>{</span> <span>VendingMachine</span> <span>machine</span> <span>=</span> <span>new</span> <span>VendingMachine</span><span>();</span> <span>machine</span><span>.</span><span>insertCredit</span><span>();</span> <span>// Insert credit → Changes to "With Credit"</span> <span>machine</span><span>.</span><span>dispenseBeverage</span><span>();</span> <span>// Dispense beverage → Changes to "Dispensing"</span> <span>machine</span><span>.</span><span>dispenseBeverage</span><span>();</span> <span>// Complete transaction → Returns to "No Credit"</span> <span>machine</span><span>.</span><span>ejectCredit</span><span>();</span> <span>// No credit to eject</span> <span>}</span> <span>}</span>public class VendingMachineTest { public static void main(String[] args) { VendingMachine machine = new VendingMachine(); machine.insertCredit(); // Insert credit → Changes to "With Credit" machine.dispenseBeverage(); // Dispense beverage → Changes to "Dispensing" machine.dispenseBeverage(); // Complete transaction → Returns to "No Credit" machine.ejectCredit(); // No credit to eject } }
Enter fullscreen mode Exit fullscreen mode
Expected Output
Credit inserted. You can now buy a beverage.Beverage dispensed. Enjoy!Beverage already dispensed. Waiting for next transaction.No credit to eject.Credit inserted. You can now buy a beverage. Beverage dispensed. Enjoy! Beverage already dispensed. Waiting for next transaction. No credit to eject.Credit inserted. You can now buy a beverage. Beverage dispensed. Enjoy! Beverage already dispensed. Waiting for next transaction. No credit to eject.
Enter fullscreen mode Exit fullscreen mode
Advantages of the State Pattern
Encapsulation of Behavior – Each state has its own behavior, making the code easier to maintain.
Eliminates Complex Conditionals – Avoids large if-else
or switch
statements in the context class.
Easier to Add New States – You can introduce new states without modifying the existing ones.
Improved Code Readability – The logic for each state is clearly separated, making the code more organized.
Disadvantages of the State Pattern
Increased Number of Classes – Requires a new class for each state, which can lead to more complex structures.
Overhead in Simple Scenarios – If an object has only a few states, a simple switch
statement may be more efficient.
Conclusion
The State Pattern is a powerful tool when dealing with objects that change behavior based on their state. By encapsulating state-specific behavior into separate classes, we create a cleaner, more maintainable, and scalable design. However, if the number of states is minimal, the overhead of additional classes may not be worth it.
Reference
Project Repository
Talk to me
Design Patterns in Java (5 Part Series)
1 Strategy Design Pattern in Java
2 Template Method Pattern in Java
3 Factory Method in Java
4 Bridge Pattern in Java
5 State Design Pattern in Java
暂无评论内容