State Design Pattern - Java - Explained

Intent

Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.
It's a behavioral design pattern which is also known as "Objects for a state"

Motivation behind the pattern/problem it solves?

The main motivation behind this pattern is to simplify complex conditional logic that depends on the state of an object.

The state design pattern can be useful in situations where an object needs to change its behavior based on its internal state.

A famous example of a state pattern is a vending machine (which is explained in the head first design pattern book), consider a vending machine that dispenses different items depending on the amount of money inserted by the user. The vending machine's behavior would be different depending on whether it is in a "ready" state, a "dispensing" state, or a "sold out" state.

State design pattern helps to improve the maintainability and extensibility of code by separating the behavior of an object into separate state classes, and allowing the object to switch between states as its internal state changes.

Simple Design Problem

We have a requirement where order object can either be in

  1. New Order 2) Shipped 3) Delivered 4) Cancelled state.

Below are the conditions which must be satisfied any time order state is changed.

  1. A shipped, delivered and cancelled order cannot be cancel .

  2. Delivered order cannot be shipped

  3. New order only be cancelled before it gets shipped .
    In short, from each step there are some rules which allow or restrict the state movement or actions restrictions.

Solution and structure :
Without the state pattern, the approach would be to create multiple if-else in the order class itself, however, with the state pattern we can create an order class and as per the order state we can delegate the request for a different operation to respective state classes.

state design pattern UML - sources wikipedia

In UML above, below are the classes we are going to create
state interface : OrderStatestate concrete classes : NewOrderState, ShippedOrderState, DeliveredOrderState, CancelledOrderStatecontext class : OrderMain client class : StatePatternMain .

click here for github code link

public interface OrderState {
    void cancel(Order order);

    void ship(Order order);

    void deliver(Order order);
}

***
public class NewOrderState implements OrderState {
    @Override
    public void cancel(Order order) {
        order.setState(new CancelledOrderState());
        System.out.println("Order cancelled");
    }

    @Override
    public void ship(Order order) {
        order.setState(new ShippedOrderState());
        System.out.println("Order shipped");
    }

    @Override
    public void deliver(Order order) {
        System.out.println("Cannot deliver an order that has not been shipped");
    }
}

**
public class ShippedOrderState implements OrderState {
    @Override
    public void cancel(Order order) {
        System.out.println("Cannot cancel a shipped order");
    }

    @Override
    public void ship(Order order) {
        System.out.println("Cannot ship an order that has already been shipped");
    }

    @Override
    public void deliver(Order order) {
        order.setState(new DeliveredOrderState());
        System.out.println("Order delivered");
    }
}

**
public class DeliveredOrderState implements OrderState {
    @Override
    public void cancel(Order order) {
        System.out.println("Cannot cancel a delivered order");
    }

    @Override
    public void ship(Order order) {
        System.out.println("Cannot ship a delivered order");
    }

    @Override
    public void deliver(Order order) {
        System.out.println("Cannot deliver an order that has already been delivered");
    }

}

***
public class CancelledOrderState implements OrderState {
    @Override
    public void cancel(Order order) {
        System.out.println("Cannot cancel a cancelled order");
    }

    @Override
    public void ship(Order order) {
        System.out.println("Cannot ship a cancelled order");
    }

    @Override
    public void deliver(Order order) {
        System.out.println("Cannot deliver a cancelled order");
    }
}

**
public class Order {
    private OrderState state;

    public Order() {
        this.state = new NewOrderState();
    }

    public void setState(OrderState state) {
        this.state = state;
    }

    public void cancel() {
        state.cancel(this);
    }

    public void ship() {
        state.ship(this);
    }

    public void deliver() {
        state.deliver(this);
    }

    public OrderState getState() {
        return state;
    }
}

**
public class StatePatternMain {
    public static void main(String[] args) {
        Order order1 = new Order();
        System.out.println("Example one - A shipped, delivered and already cancelled order cannot be cancel.");
        System.out.println("order1 current state is - " + order1.getState());
        order1.cancel();
        System.out.println("order1 current state is - " + order1.getState());
        order1.ship();
        order1.deliver();
        System.out.println("order1 current state is - " + order1.getState());

        Order order2 = new Order();
        System.out.println("\nExample two - Delivered order cannot be shipped");
        System.out.println("order2 current state is - " + order2.getState());
        order2.ship();
        order2.deliver();
        order2.ship();
        System.out.println("order2 current state is - " + order2.getState());

        Order order3 = new Order();
        System.out.println("\nExample three - New order only be cancelled before it gets shipped");
        System.out.println("order3 current state is - " + order3.getState());
        order3.cancel();
        System.out.println("order3 current state is - " + order3.getState());
    }
}


*********** output ************** 
Example one - A shipped, delivered and already cancelled order cannot be cancel.
order1 current state is - com.ecommercearchitect.state.NewOrderState@19dfb72a
Order cancelled
order1 current state is - com.ecommercearchitect.state.CancelledOrderState@7e0ea639
Cannot ship a cancelled order
Cannot deliver a cancelled order
order1 current state is - com.ecommercearchitect.state.CancelledOrderState@7e0ea639

Example two - Delivered order cannot be shipped
order2 current state is - com.ecommercearchitect.state.NewOrderState@3d24753a
Order shipped
Order delivered
Cannot ship a delivered order
order2 current state is - com.ecommercearchitect.state.DeliveredOrderState@71be98f5

Example three - New order only be cancelled before it gets shipped
order3 current state is - com.ecommercearchitect.state.NewOrderState@6fadae5d
Order cancelled
order3 current state is - com.ecommercearchitect.state.CancelledOrderState@17f6480

****END****

Applicability

Here are some situations where the state design pattern can be applied:

State-dependent behavior: If an object's behavior depends on its internal state, it can be a good candidate for the state design pattern. This could be anything from a vending machine to a game character.

Complex conditional logic: When an object has a complex conditional logic (polluted with lots of if/else) that is difficult to maintain and extend, using the state design pattern can help to simplify the code and make it more modular.

Multiple states: If an object has multiple states that it can be in, it can be a good idea to use the state design pattern to manage those states and their associated behavior.

Encapsulation: The state design pattern can be used to encapsulate the behavior of an object and separate it into discrete state classes. This can help to improve the maintainability and extensibility of code. ( does it sounds similar to the strategy pattern ?? keep reading further and check comparison with strategy pattern below )

Flexibility: The state design pattern can make an object more flexible and adaptable to changes in its behaviour. For example, if new states need to be added, it can be easier to do so with the state design pattern in place.

Pros and Cons

Pros

  1. Enhances readability: The state design pattern can make code more readable and easier to understand by separating the behavior of an object into different states.

  2. Increases flexibility (open/close principle): The state design pattern makes an object more flexible and adaptable to changes in behavior, making it easier to add new states or modify existing ones.

  3. Encourages modularity: The state design pattern promotes modularity and encapsulation by separating an object's behavior into discrete states.

  4. Simplifies complex logic: The state design pattern can help to simplify complex conditional logic by breaking down the behavior of an object into separate state classes.

Cons

  1. Overhead: Implementing the state design pattern can add some overhead to code because of the need to create separate state classes.

  2. Not always necessary: In some cases, the state design pattern may not be necessary, and simple conditional statements may be sufficient to manage an object's behavior.

  3. Can be overused: It is possible to overuse the state design pattern and create too many state classes, leading to unnecessary complexity.

Difference between strategy and state pattern

The State and Strategy design patterns are both behavioral patterns in Java that allow you to change the behavior of an object dynamically. However, there are some key differences between the two.

Purpose: The purpose of the Strategy pattern is to change the behavior of an object at runtime, whereas the purpose of the State pattern is to change the behavior of an object based on its internal state.

Implementation: The Strategy pattern is implemented using composition, where the context object holds a reference to a strategy object that provides the behavior to be executed. On the other hand, the State pattern is implemented using state classes that encapsulate the behavior associated with a particular state.

Relationship: In the Strategy pattern, the client code has the responsibility of selecting the appropriate strategy object at runtime. In the State pattern, the state transitions are usually triggered by the object's internal state changes and the object transitions from one state to another automatically.

Flexibility: The Strategy pattern provides more flexibility in changing the behavior of an object since the client code can switch between different strategies at runtime. In contrast, the State pattern provides less flexibility as the behavior of an object is restricted by its internal state.

In summary, the main difference between the State and Strategy design patterns is that the Strategy pattern focuses on changing the behavior of an object based on external conditions, while the State pattern focuses on changing the behavior of an object based on its internal state.
Also,
The strategy pattern provides a better alternative to subclassing, while in state pattern – behavior is encapsulated in separate classes.

Relations with other patterns

  1. Singleton Pattern : The state design pattern can be used in conjunction with the singleton pattern, which ensures that only one instance of an object is created. This can be useful when the behavior of an object needs to be managed in a centralized way.

  2. Observer Pattern: The state design pattern can be used in conjunction with the observer pattern, which allows an object to notify its observers when its state changes. This can be useful when multiple objects need to be notified of changes in the state of an object.

  3. Strategy Pattern: The state design pattern is often used in conjunction with the strategy pattern, which allows an object to dynamically change its behavior by selecting a different algorithm at runtime. The state design pattern can be used to manage the different states that an object can be in, while the strategy pattern can be used to define the algorithms that the object can use in each state.

please #### click here for the complete code on github.

Thanks for reading !!! Love IT Live IT Enjoy IT !!!! Happy Coding !!!

0
Subscribe to my newsletter

Read articles from Aditya Chaudhari directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Aditya Chaudhari
Aditya Chaudhari

building efficient, scalable and maintainable enterprise e-commerce applications | Technical Architect | SAP Commerce Cloud (hybris) | Java | Spring | Mirakl