Seth Barrett

Daily Blog Post: April 6th, 2023

design1

Apr 6th, 2023

Implementing the State Design Pattern: Examples in Java
Introduction:

In this post, we will be discussing the State Design Pattern, one of the behavioral design patterns. This pattern is used when an object changes its behavior based on its internal state. This pattern allows us to encapsulate the state of an object and delegate the state transitions to different objects that represent each state.

Overview:

The State Design Pattern consists of three main components:

  1. Context: The object whose behavior is dependent on its internal state. It contains a reference to the current state object and also defines the interface through which the state objects can interact with the context object.
  2. State: The objects that represent the different states of the context object. Each state object implements the same interface as the context object, defining the behavior that is specific to that state.
  3. Transition: The mechanism that transitions the context object from one state to another. This can be implemented using a State Machine or a series of conditional statements.

Benefits:

  1. Encapsulates state-specific behavior into separate classes, making the code more modular and easier to maintain.
  2. Simplifies complex conditional logic that is dependent on the object's state.
  3. Allows for new states to be added without modifying the existing code.

Example:

Consider a vending machine that dispenses different types of products based on its current state. The vending machine has three states: NoMoneyState, HasMoneyState, and SoldState. The NoMoneyState allows the user to insert money, while the HasMoneyState allows the user to select a product. The SoldState dispenses the product and transitions the vending machine back to the NoMoneyState.

Let's see how this can be implemented using the State Design Pattern:

    // Context Class
public class VendingMachine {
    private State currentState;
    
    // Constructor
    public VendingMachine() {
        this.currentState = new NoMoneyState();
    }
    
    // Setter method for changing the state
    public void setCurrentState(State currentState) {
        this.currentState = currentState;
    }
    
    // Delegate method to the current state
    public void insertMoney(int amount) {
        this.currentState.insertMoney(amount, this);
    }
    
    // Delegate method to the current state
    public void selectProduct(int productId) {
        this.currentState.selectProduct(productId, this);
    }
    
    // Delegate method to the current state
    public void dispenseProduct() {
        this.currentState.dispenseProduct(this);
    }
}

// State Interface
public interface State {
    public void insertMoney(int amount, VendingMachine vendingMachine);
    public void selectProduct(int productId, VendingMachine vendingMachine);
    public void dispenseProduct(VendingMachine vendingMachine);
}

// No Money State
public class NoMoneyState implements State {
    public void insertMoney(int amount, VendingMachine vendingMachine) {
        vendingMachine.setCurrentState(new HasMoneyState());
    }
    
    public void selectProduct(int productId, VendingMachine vendingMachine) {
        System.out.println("Please insert money first");
    }
    
    public void dispenseProduct(VendingMachine vendingMachine) {
        System.out.println("Please insert money and select a product first");
    }
}

// Has Money State
public class HasMoneyState implements State {
    public void insertMoney(int amount, VendingMachine vendingMachine) {
        System.out.println("You have already inserted money");
    }
    
    public void selectProduct(int productId, VendingMachine vendingMachine) {
        vendingMachine.setCurrentState(new SoldState());
    }
    
    public void dispenseProduct(VendingMachine vendingMachine) {
        System.out.println("Please select a product first");
    }
}

// Sold State
public class SoldState implements State {
    public void insertMoney(int amount, VendingMachine vendingMachine) {
        System.out.println(amount + vendingMachine);
    }
}
public class Fan {
    private State currentState;

    public Fan() {
        currentState = new OffState();
    }

    public void setState(State state) {
        currentState = state;
    }

    public void pullChain() {
        currentState.handleRequest(this);
    }
}

interface State {
    void handleRequest(Fan fan);
}

class OffState implements State {
    @Override
    public void handleRequest(Fan fan) {
        System.out.println("Turning fan on to low.");
        fan.setState(new LowState());
    }
}

class LowState implements State {
    @Override
    public void handleRequest(Fan fan) {
        System.out.println("Turning fan on to medium.");
        fan.setState(new MediumState());
    }
}

class MediumState implements State {
    @Override
    public void handleRequest(Fan fan) {
        System.out.println("Turning fan on to high.");
        fan.setState(new HighState());
    }
}

class HighState implements State {
    @Override
    public void handleRequest(Fan fan) {
        System.out.println("Turning fan off.");
        fan.setState(new OffState());
    }
}

In this example, we have a Fan class that has a current state, represented by an instance of the State interface. The Fan class has a method to set the state and a method to pull the fan's chain, which triggers the handleRequest method of the current state.

Each concrete state class (i.e. OffState, LowState, MediumState, and HighState) implements the State interface and provides its own implementation of the handleRequest method. In this example, each state sets the Fan's state to the next appropriate state when the chain is pulled.

This example demonstrates how the State Pattern allows an object to change its behavior when its internal state changes. In this case, the Fan's behavior changes based on its current speed state, represented by the State interface. By separating the behavior of the Fan into different states, we can easily add new behavior in the future by adding a new state class that implements the State interface.