In the state pattern, request handling takes into account the current state of the object. This tutorial explains State pattern with Java examples.

What is State Pattern?

The state pattern is a behavioral design pattern which can be used to represent state machine in code. It is used to localize the state behavior. The state of an object is stored in objects rather than local variables of different types. This is used to represent finite state machine. At a given point in time, the program can be in one of the finite states. For each state, the system behaves diferently. Depending on the current state of the system, it may or may not switch to other states. A simple example of state pattern is followed by switches in home. The switch can be in one of the two states: on or off. If the switch is OFF, it can switch to only ON state.

This pattern can be applied to work progress boards. The issue can only move to IN PROGRESS from BACKLOG or BLOCKED state. It can be moved to DONE only from IN PROGRESS state, etc. We could implement this using conditionals making sure if current state is BACKLOG, it can move to only IN PROGRESS. This might be ok for only few states but what if we have lots of states which can go through. In that case, our code will have huge if...else blocks. It will be difficult to manage because even a simple logic change will require lot more attention to modify the code.

The state pattern recommends to create a new class for all possible states of an object and methods for specific behaviors.

How to implement State Pattern?

The state pattern consists of following actors:

  • Context: This stores a reference to one of the concrete states. The context object will communicate with states using state interface.
  • State: This is an interface that defines required methods for moving state from one state to another. There will be implementations of this interface with concrete states. This state object may store a reference to the context object which can be useful to fetch any required info from the context.

Practical Code - Washing Machine states

  1. Define an interface for State objects. Note that each operation takes a context washing machine object.
1public interface WashingMachineState {
2    void start(WashingMachine machine);
3    void chooseCycle(WashingMachine machine);
4    void pause(WashingMachine machine);
5    void stop(WashingMachine machine);
6    void openDoor(WashingMachine machine);
7    String getStatus();
8}
  1. Define concrete states for each of these. Depending on current state, it may not be able to switch to other state. For example, in OffState, the system cannot transition to PausedState.

    a. OffState for machine

     1public class OffState implements WashingMachineState {
     2    @Override
     3    public void start(WashingMachine machine) {
     4        System.out.println("Select wash type first");
     5    }
     6
     7    @Override
     8    public void chooseCycle(WashingMachine machine) {
     9        System.out.println("Cycle selected");
    10        machine.setState(machine.getWashingState());
    11    }
    12
    13    @Override
    14    public void pause(WashingMachine machine) {
    15        System.out.println("Machine is already off so cannot pause.");
    16    }
    17
    18    @Override
    19    public void stop(WashingMachine machine) {
    20        System.out.println("Machine is already off so cannot stop.");
    21    }
    22
    23    @Override
    24    public void openDoor(WashingMachine machine) {
    25        System.out.println("You can open the doors.");
    26    }
    27
    28    @Override
    29    public String getStatus() {
    30        return "Off";
    31    }
    32}
    

    b. CycleSelected state

     1public class CycleSelectedState implements WashingMachineState {
     2    @Override
     3    public void start(WashingMachine machine) {
     4        System.out.println("Starting the washing machine");
     5        machine.setState(machine.getWashingState());
     6    }
     7
     8    @Override
     9    public void chooseCycle(WashingMachine machine) {
    10        System.out.println("Cycle selected");
    11    }
    12
    13    @Override
    14    public void pause(WashingMachine machine) {
    15        System.out.println("Cannot pause while cycle selected");
    16    }
    17
    18    @Override
    19    public void stop(WashingMachine machine) {
    20        System.out.println("Stopping the washing machine");
    21        machine.setState(machine.getOffState());
    22    }
    23
    24    @Override
    25    public void openDoor(WashingMachine machine) {
    26        System.out.println("Cannot open the door while cycle selected");
    27    }
    28
    29    @Override
    30    public String getStatus() {
    31        return "Cycle Selected";
    32    }
    33}
    

    c. PausedState

     1public class PausedState implements WashingMachineState {
     2    @Override
     3    public void start(WashingMachine machine) {
     4        System.out.println("Resuming the washing machine");
     5        machine.setState(machine.getWashingState());
     6    }
     7
     8    @Override
     9    public void chooseCycle(WashingMachine machine) {
    10        System.out.println("Cannot choose cycle while paused");
    11    }
    12
    13    @Override
    14    public void pause(WashingMachine machine) {
    15        System.out.println("Already paused");
    16    }
    17
    18    @Override
    19    public void stop(WashingMachine machine) {
    20        System.out.println("Stopping the washing machine");
    21        machine.setState(machine.getOffState());
    22    }
    23
    24    @Override
    25    public void openDoor(WashingMachine machine) {
    26        System.out.println("You can open the door now.");
    27    }
    28
    29    @Override
    30    public String getStatus() {
    31        return "Paused";
    32    }
    33}
    

    d. WashingState state

     1public class WashingState implements WashingMachineState {
     2    @Override
     3    public void start(WashingMachine machine) {
     4        System.out.println("Already washing");
     5    }
     6
     7    public void chooseCycle(WashingMachine machine) {
     8        System.out.println("Already washing");
     9    }
    10
    11    @Override
    12    public void pause(WashingMachine machine) {
    13        System.out.println("Pausing the washing machine");
    14        machine.setState(machine.getPauseState());
    15    }
    16
    17    @Override
    18    public void stop(WashingMachine machine) {
    19        System.out.println("Stopping the washing machine");
    20        machine.setState(machine.getOffState());
    21    }
    22
    23    @Override
    24    public void openDoor(WashingMachine machine) {
    25        System.out.println("Cannot open the door while washing");
    26    }
    27
    28    @Override
    29    public String getStatus() {
    30        return "Washing";
    31    }
    32}
    
  2. Next, we have WashingMachine which will have methods for normal machine operations. Note that washing machine has a current state as a member.

 1public class WashingMachine {
 2    private WashingMachineState state;
 3
 4    public WashingMachine() {
 5        state = new OffState();
 6    }
 7
 8    public void start() {
 9        state.start(this);
10    }
11
12    public void chooseCycle() {
13        state.chooseCycle(this);
14    }
15
16    public void pause() {
17        state.pause(this);
18    }
19
20    public void stop() {
21        state.stop(this);
22    }
23
24    public void openLid() {
25        state.openDoor(this);
26    }
27
28    public String getStatus() {
29        return state.getStatus();
30    }
31
32    public void setState(WashingMachineState state) {
33        this.state = state;
34    }
35
36    public WashingMachineState getState() {
37        return state;
38    }
39
40    public WashingMachineState getOffState() {
41        return new OffState();
42    }
43
44    public WashingMachineState getCycleSelectedState() {
45        return new CycleSelectedState();
46    }
47
48    public WashingMachineState getPauseState() {
49        return new OffState();
50    }
51
52    public WashingMachineState getWashingState() {
53        return new WashingState();
54    }
55}

Now, the client code can work with washing machine.

 1public class ClientMain {
 2    public static void main(String[] args) {
 3        WashingMachine washingMachine = new WashingMachine(); // OffState
 4        System.out.println("Current state: " + washingMachine.getStatus()); // Off
 5        washingMachine.start(); // Select wash type first
 6        washingMachine.chooseCycle(); // Cycle selected
 7        System.out.println("Current state: " + washingMachine.getStatus()); // Cycle Selected
 8        washingMachine.openLid(); // Cannot open the door while cycle selected
 9        washingMachine.start(); // Starting the washing machine
10        System.out.println("Current state: " + washingMachine.getStatus()); // Washing
11        washingMachine.start(); // Already washing
12    }
13}

Advantages:

  • The code becomes easier to understand with each state in its own class.
  • It also becomes easier to add new states. We can add new state class without modifying existing state objects.

Disadvantages:

  • Having implemented this pattern, we might end up with lots of classes for each state. Having new object for each of states may result in memory overhead.
  • If you have very few state changes, this pattern might be an overkill.

Comparison between State and Strategy Patterns

State PatternStrategy Pattern
This is interface driven.This is based on algorithm or strategy.
Each state only knows about the next state.Different strategies don’t know about each other. They are independent.
Each state has its own classIn this case, each algorithm has their own class.

Summary

  • This pattern can be used to implement Finite State machine for the system.
  • It can be little complicated if there are lots of state changes happening. It also adds lots of state classes.
  • It simplifies complexities of having multiple if..else conditions and make code more maintainable. We can easily add new state by adding new class without modifying existing states. This follows Open/Closed principle.
  • State pattern looks similar to Strategy pattern, but the intent is different.