How to implement State Pattern in Java?

Photo by Mister Mister on Pexels.com

Let’s say you run an eCommerce store.

You want to develop an application to run your eCommerce.

You are specifically concerned about the delivery of order items.

When a customer places an order it goes through many different states:

  • Order is received
  • Order is packed
  • Order is shipped
  • Order is in transit
  • Order is delivered

Also Order can be cancelled.

So delivery of order items can be in any of the above different states at a given time.

When you want to do something on a particular order , its behavior is dependent on its current state.

For example ,

If the order is in received status or in packed status we can allow customer to cancel the order.

But if the order is in any other state we can’t allow customer to cancel the order.

Using State Pattern , we are going to encapsulate a state specific behavior in a separate class.

So if customer issues a cancel request , the ECommerceStore will delegate the request to the class which represents its current state.

That class will decide how to respond to the request.

This way the eCommerce Store does not know how it behaves , we decouple it and delegate to another class which represents the delivery status.

Let’s look at the client code :

package behavioural.state.pattern;

public class Client {

	public static void main(String a[]) {

		ECommerceStore store = new ECommerceStore();

		System.out.println("Scenario 1 - Place order and cancel it before it get shipped");

		store.getOrder();

		store.updateStatus();

		store.enquireStatus();

		store.cancelOrder();

		store.enquireStatus();

		System.out.println();

		System.out.println("Scenario 2 - Place order and cancel it while in transit");

		store.getOrder();

		store.enquireStatus();

		store.updateStatus();

		store.enquireStatus();

		store.updateStatus();

		store.enquireStatus();

		store.updateStatus();

		store.cancelOrder();

		store.enquireStatus();

		System.out.println();
		System.out.println("Scenario 3 - Place order and wait until it gets delivered");

		store.getOrder();
		store.updateStatus();
		store.updateStatus();
		store.updateStatus();
		store.updateStatus();
		store.enquireStatus();

	}
}

I am considering three scenarios here:

  1. Customer places an order and cancels it before it is shipped
  2. Customer places an order and tries to cancel it while in transit
  3. Customer places an order and waits for delivery

When the eCommerce store gets the order (through getOrder()) method , I am setting its initial state to OrderReceivedStatus (this is represented by the class OrderReceivedStatus.java).

For every state we have a class.

And we have the following classes to represent the different states:

1.OrderReceivedStatus

2.ItemsPackedStatus

3.ItemsShippedStatus

4.InTransitStatus

5.ItemsDeliveredStatus

6.OrderCancelledStatus

The store updates it state through updateStatus() method.

Say , if the order has just been received and the store owner has packed the items , he can call updateStatus() method which will move the current state of the delivery from ‘OrderReceivedStatus’ to ‘ItemPackedStatus’.

And when the store again calls updateStatus() method , it again moves from the now current status ‘ItemPackedStatus’ to ‘ItemShippedStatus’.

At any state when you call enquireStatus() method , the current status object (the reference of which is present in ECommerceStore object) displays it’s status.

Also at any state when you call cancelOrder() , the current state class decides how to respond to it.

If items are already in transit and you are in InTransitStatus , then the class InTransitStatus throws an error saying “Order cannot be cancelled as items are already in transit”

Let’s look at the ECommerceStore class:

package behavioural.state.pattern;

public class ECommerceStore {

	private DeliveryStatus deliveryStatus;

	public void getOrder() {

		// intial status
		setStatus(new OrderReceivedStatus(this));

	}

	public void enquireStatus() {

		this.deliveryStatus.enquireStatus();

	}

	public void cancelOrder() {

		System.out.println("Cancelling Order...");
		this.deliveryStatus.cancelOrder();
	}

	public void updateStatus() {

		this.deliveryStatus.updateStatus();

	}

	public void setStatus(DeliveryStatus deliveryStatus) {

		this.deliveryStatus = deliveryStatus;

	}

}

As you can see , the initial state is set in getOrders() method.

And after that none of the methods are implemented by the ECommerceStore class itself.

They are delegated to the current ‘state class’.

It has one method setStatus() to update the current status which is called by the ‘State’ classes to move to the next state.

This can be understood by looking at one of the state classes.

package behavioural.state.pattern;

public class OrderReceivedStatus implements DeliveryStatus {

	private ECommerceStore store;

	public OrderReceivedStatus(ECommerceStore store) {

		this.store = store;
	}

	@Override
	public void enquireStatus() {

		System.out.println("Order Received , Please wait for items to get packed");

	}

	@Override
	public void updateStatus() {

		this.store.setStatus(new ItemsPackedStatus(this.store));

	}

	@Override
	public void cancelOrder() {

		this.store.setStatus(new OrderCancelledStatus(this.store));

	}

}

As you can see , eCommerceStore object is passed as an argument to the constructor of OrderReceivedStatus class. This way it will have a reference to the store.

When you call updateStore() on the store object from client code , it calls updateStatus() method in the above class. This class then sets the next state on the store using the below line :

		this.store.setStatus(new ItemsPackedStatus(this.store));

The state is now moved to ItemPackedStatus! That’s how the state transition happens.

Look at the cancelOrder() method.

Since user can cancel orders when order is just in received status , we are setting the state to OrderCancelledStatus inside this method.

Now let’s look at a different class :

package behavioural.state.pattern;

public class InTransitStatus implements DeliveryStatus {

	private ECommerceStore store;

	public InTransitStatus(ECommerceStore store) {

		this.store = store;
	}

	@Override
	public void enquireStatus() {

		System.out.println("Items in Transit , please wait for delivery");

	}

	@Override
	public void updateStatus() {

		this.store.setStatus(new ItemsDeliveredStatus(this.store));

	}

	@Override
	public void cancelOrder() {

		System.out.println("You cannot cancel the order , it is already in transit status");

	}

}

Look at the cancelOrder() method here.

It says order cannot be cancelled as Item is already in transit.

Also look at the updateStatus() method . The next state to InTransitStatus is ItemsDeliveredStatus and hence this is set inside this method.

All the Status classes implement the below interface:

package behavioural.state.pattern;

public interface DeliveryStatus {
	
	
	void enquireStatus();
	
	void updateStatus();
	
	void cancelOrder();
	

}

Here is the output of the client code:

Scenario 1 - Place order and cancel it before it get shipped
Items are packed , Please wait for it to get shipped
Cancelling Order...
Order Cancelled.

Scenario 2 - Place order and cancel it while in transit
Order Received , Please wait for items to get packed
Items are packed , Please wait for it to get shipped
Items Shipped , Please wait for transit
Cancelling Order...
You cannot cancel the order , it is already in transit status
Items in Transit , please wait for delivery

Scenario 3 - Place order and wait until it gets delivered
Items Delivered to Customer.

For the last scenario I have removed the enquireStatus() call for every status update except the final one and so you see a single status.

Voila!

The state transitions smoothly and the behavior of an eCommerceStore(with respect to delivery) is implemented in its state specific classes!

That’s the State pattern in action.

Quoting the Gang of Four:

Allow an object to alter its behaviour when its internal state changes. The object will appear to change its class

In short , we have encapsulated the state specific behavior of an object to different classes each representing a specific state.

Here is the UML diagram for reference:

Here is the code:

https://github.com/vijaysrj/designPatternsGoF/tree/master/src/behavioural/state/pattern

Comments

Leave a Reply

Discover more from The Full Stack Developer

Subscribe now to keep reading and get access to the full archive.

Continue reading