State is a behavioural design pattern that allows object to alter its behaviour when its internal state changes. There is a finite number of states which a program can be in, within any unique state, the program behaves differently, and the program can switch from on state to another. Each concrete state class knows which state should come next. The state object can be pre-created for further use or can be created when needed.

Applicability

  • You want object to behave differently depending on its current state.
  • You have complex conditional statements to manage an object’s behaviour based on its state
  • You anticipate your object will need to support additional states in the future.

Approach

  1. Identify the context by determine the object for which you want to manage different states.
  2. Define the state interface that represents the various states and behaviours the context can perform when in different states.
  3. Create concrete states that each represents a specific state.

Components

  • Context
    • It is the client’s interface to the state machine
    • Maintain an instance of a concrete state that defines the current state.
  • State (abstract)
    • Defines an interface for all concrete states, each method in the interface corresponds to a possible behaviour that can be performed at a particular state.
  • Concrete state
    • Each concrete state implements the abstract state with encapsulated behaviour associated with a specific state of context.

Example 1

Pre-create state objects and never destroy them

  • Useful for frequent state changes, save costs of recreating states
  • Context must keep reference to all states

This example models a simple fan with three states, the fan states can be adjusted.

Identify state pattern components

Context: Fan State: FanState Concrete state: OffState, LowState,HighState

State

State interface defines the common actions can be performed on the fan, it defines the contracts for the concrete states.

interface FanState {
    void turnOn();
    void turnOff();
    void increaseSpeed();
    void decreaseSpeed();
}

Concrete state

Each concrete state implements the state interface, providing specific behaviour for actions defined in the interface.

class OffState implements FanState {
    @Override
    public void turnOn() {
        System.out.println("Turning the fan on to low speed.");
        fan.setState(fan.getLowState());
    }
 
    @Override
    public void turnOff() {
        System.out.println("Fan is already off.");
    }
 
    @Override
    public void increaseSpeed() {
        System.out.println("Can't increase speed. Fan is off.");
    }
 
    @Override
    public void decreaseSpeed() {
        System.out.println("Can't decrease speed. Fan is off.");
    }
}
class LowState implements FanState {
    @Override
    public void turnOn() {
        System.out.println("Fan is already on at low speed.");
    }
 
    @Override
    public void turnOff() {
        System.out.println("Turning the fan off.");
        fan.setState(fan.getOffState());
    }
 
    @Override
    public void increaseSpeed() {
        System.out.println("Increasing fan speed to high.");
        fan.setState(fan.getHighState());
    }
 
    @Override
    public void decreaseSpeed() {
        System.out.println("Can't decrease speed. Fan is already on low.");
    }
}
class HighState implements FanState {
    @Override
    public void turnOn() {
        System.out.println("Fan is already on at high speed.");
    }
 
    @Override
    public void turnOff() {
        System.out.println("Turning the fan off.");
        fan.setState(fan.getOffState());
    }
 
    @Override
    public void increaseSpeed() {
        System.out.println("Can't increase speed. Fan is already on high.");
    }
 
    @Override
    public void decreaseSpeed() {
        System.out.println("Decreasing fan speed to low.");
        fan.setState(fan.getLowState());
    }
}

Context

The context Fan maintains a reference to the current state. The context is responsible for transitioning between different states by setting the current state. The states are pre-created for state switching and will never be destroyed.

class Fan {
    private FanState currentState;
    private FanState offState; 
    private FanState lowState; 
    private FanState highState;
 
    public Fan() {
	    // states are pre-created
	    offState = new OffState(); 
	    lowState = new LowState(); 
	    highState = new HighState();
        // Initially, the fan is off.
        currentState = OffState;
    }
 
    public void setState(FanState state) {
        this.currentState = state;
    }
    // getters for states
	public FanState getOffState() {
		return offState;
	}
	// ...
	
    public void turnOn() {
        currentState.turnOn();
    }
 
    public void turnOff() {
        currentState.turnOff();
    }
	// ...
}

Client

The client triggers state transitions and observing responses based on the fan’s state.

// Example Usage
public class Main {
    public static void main(String[] args) {
        Fan fan = new Fan();
 
        fan.turnOn(); // Turning on (low speed)
        fan.increaseSpeed(); // Increasing speed (high speed)
        fan.decreaseSpeed(); // Decreasing speed (low speed)
        fan.turnOff(); // Turning off
        fan.increaseSpeed(); // Trying to increase speed (fan is off)
    }
}
 

Example 2

Only when they are needed and destroy them thereafter States are not known at runtime and context change states frequently

State

The state remains unchanged from the previous example.

Concrete state

The concrete state remains unchanged from the previous example.

Context

The sate is created when needed and is unreferenced and cleaned by the JVM garbage collection mechanism afterwards.

class Fan {
	private FanState currentState;
 
	public Fan() {
		// inital state is undefined
		sate = null;
	}
 
	public void setState(FanState state) {
		this.currentState = state;
	}
 
	public void turnOn() {
		if (state == null) {
			// create state object when needed
			currentState = new LowState();
			System.out.println("Turning fan on to low spped.");
		} else {
			System.out.println("Fan is already on");
		}
	}
 
	public void turnOff() {
		if (currentState != null) {
			System.out.println("Turning fan off");
			// unreference the state and dystroyed by JVM gabage collector
			currentState = null;
		} else {
			System.out.println("Fan is already off.");
		}
	}
 
	public void increaseSpeed() {
		if (currentState != null) {
			currentState.increaseSpeed(this);
		} else {
			System.out.println("Cannot increase speed, fan is off.");
		}
	}
 
	public void decreaseSpeed() {
		if (currentState != null) {
			currentState.decreaseSpeed(this);
		} else {
			System.out.println("Cannot decrease speed, fan is off.");
		}
	}
}

Client

The concrete state remains unchanged from the previous example.

In this example, sate objects are created only when needed and destroyed afterwards. This approach allows you to create and destroy state objects dynamically based on fan’s state.


Back to super node: Behavioural Patterns

Design_PatternBehavioural_Design_PatternsSOFT2201State_pattern