State Design Pattern in Java

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 or switch-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

原文链接:State Design Pattern in Java

© 版权声明
THE END
喜欢就支持一下吧
点赞14 分享
Live every day as the last day of life.
把活着的每一天看作生命的最后一天
评论 抢沙发

请登录后发表评论

    暂无评论内容