In this tutorial, we’ll explore another popular behavioral design pattern – the State Design Pattern.
The knowledge of state design pattern comes handy when we’re working with an object which can exist in multiple states. We should use it primarily when the behavior of an object depends on its current state. This pattern helps us avoid long conditional checks of the object’s state within methods of that class.
With it, let’s get started!
We can represent the state design pattern with the following UML diagram:
Where,
Let’s say we have an automatic soap dispenser. It ideally can exist in these states – Ideal, Dispensing, or OutOfStock. Let’s visualize it with the help of a state diagram:
We first start by defining ourĀ DispenserState interface:
public DispenserState { void sensesHand(AutoDispenser autoDispenser); void stopsSensingHand(AutoDispenser autoDispenser); }
Each of our state classes must implement the DispenserState and define the behavior for that state. Our IdleState class would look like:
public class IdleState implements DispenserState { public void sensesHand(AutoDispenser autoDispenser) { System.out.println("Hand sensed"); autoDispenser.startDispensing(); autoDispenser.setDispenserState(autoDispenser.getDispensingState()); } public void stopsSensingHand(AutoDispenser autoDispenser) { System.out.println("Sensor inactive already!"); } }
Similarly, we can define our other two classes representing state:
public class DispensingState implements DispenserState { public void sensesHand(AutoDispenser autoDispenser) { System.out.println("Already dispensing"); } public void stopsSensingHand(AutoDispenser autoDispenser) { System.out.println("Stopped sensing"); autoDispenser.stopDispensing(); if(autoDispenser.getQuantity() > 1) { autoDispenser.setDispenserState(autoDispenser.getIdleState()); } else { autoDispenser.setDispenserState(autoDispenser.getOutOfStockState()); } } } public class OutOfStockState implements DispenserState { public void sensesHand(AutoDispenser autoDispenser) { System.out.println("nothing to dispense"); } public void stopsSensingHand(AutoDispenser soapDispenser) { System.out.println("dispenser is already inactive"); } }
Finally, let’s define our context class – the AutoDispenser, which will interact with the client code:
public class AutoDispenser { private IdleState idleState; private DispensingState dispensingState; private OutOfStockState outOfStockState; private DispenserState currentDispenserState; public AutoDispenser() { this.idleState = new IdleState(); this.dispensingState = new DispensingState(); this.outOfStockState = new OutOfStockState(); if(getQuantity() > 0) { this.currentDispenserState = idleState; } else { this.currentDispenserState = outOfStockState; } } public int getQuantity() { //returns current soap quantity ... } public void startDispensing() { ... } public void stopDispensing() { ... } public void sensesHand() { this.currentDispenserState.sensesHand(this); } public void stopsSensingHand() { this.currentDispenserState.stopsSensingHand(this); } }
Here, we maintain the current state of our auto-dispenser and invoke the method in the concrete state class to achieve the operation.
Apart from helping us avoid long conditionals, it also provides other benefits:
As a programmer, it’s really important for us to choose the right design pattern for solving any design problem.
In this quick tutorial, we explored the State Design Pattern. We learned how and when to implement it.