State Pattern
State design pattern is one of the behavioural design patterns.
This pattern is used when an Object changes its behaviour based on its internal state.
Example :
Assume some electronic device is an object which has 2 states called “ON” and “OFF”
This object behaves differently when the state changes.
When the state is “ON” it will turn on the device and when the state is “OFF” it will turn off the device
If we want to execute different behaviour based on state change, we can do it using if-else conditions
But, State pattern provides the solution for the same in a more loosely coupled way.
State pattern uses 2 important concepts called “context” and “State” to achieve the same.
Let’s understand these 2 concepts
State defines a class with separate implementations for each behaviour with parent abstract class or Interface
Context is the class that has a reference to one of the concrete implementations of the State.
Context forwards the request to the state object for processing
Let’s try to understand State pattern with example
Consider above example of electronic device, it has 2 states “ON” and “OFF”
Based on the state, corresponding behaviour of the device will change
If we implement this requirement without state pattern, we need to use if-else blocks as below
- package com.kb.state;
- public class ElectronicDevice {
- private String state;
- public void setState(String state){
- this.state=state;
- }
- public void execute(){
- if(state.equalsIgnoreCase("ON")){
- System.out.println("Device is turned ON");
- }else if(state.equalsIgnoreCase("OFF")){
- System.out.println("Device is turned OFF");
- }
- }
- public static void main(String args[]){
- ElectronicDevice device = new ElectronicDevice();
- device.setState("ON");
- device.execute();
- device.setState("OFF");
- device.execute();
- }
- }
package com.kb.state; public class ElectronicDevice { private String state; public void setState(String state){ this.state=state; } public void execute(){ if(state.equalsIgnoreCase("ON")){ System.out.println("Device is turned ON"); }else if(state.equalsIgnoreCase("OFF")){ System.out.println("Device is turned OFF"); } } public static void main(String args[]){ ElectronicDevice device = new ElectronicDevice(); device.setState("ON"); device.execute(); device.setState("OFF"); device.execute(); } }
If we observe this implementation, we could see that, if-else condition is tightly coupled with states.
Let’s try to implement the same using State design pattern
Step 1
First of all, We need to create State interface
This will define the method that should be implemented by different concrete states classes and context class.
- package com.kb.state;
- public interface State {
- public void execute();
- }
package com.kb.state; public interface State { public void execute(); }
We have defined State interface and declared a method called “execute()”
Step 2
In our requirement, we will have 2 states – one for turning Device on and another to turn it off.
So, we need to create 2 concrete state implementations for these behaviours.
- package com.kb.state;
- public class DeviceOnState implements State {
- @Override
- public void execute() {
- System.out.println("Device is turned ON");
- }
- }
- package com.kb.state;
- public class DeviceOffState implements State {
- @Override
- public void execute() {
- System.out.println("Device is turned OFF");
- }
- }
package com.kb.state; public class DeviceOnState implements State { @Override public void execute() { System.out.println("Device is turned ON"); } } package com.kb.state; public class DeviceOffState implements State { @Override public void execute() { System.out.println("Device is turned OFF"); } }
Step 3
Create a context class which implements State and holds a reference to State object
- package com.kb.state;
- public class DeviceContext implements State {
- private State state;
- @Override
- public void execute() {
- state.execute();
- }
- public State getState() {
- return state;
- }
- public void setState(State state) {
- this.state = state;
- }
- }
package com.kb.state; public class DeviceContext implements State { private State state; @Override public void execute() { state.execute(); } public State getState() { return state; } public void setState(State state) { this.state = state; } }
We can see that Context class holds a reference to current state and makes a request to actual state implementation
Step 4
Let’s test this pattern by creating a client program
- package com.kb.state;
- public class Refrigerator {
- public static void main(String[] args) {
- DeviceContext context = new DeviceContext();
- State refrigeratorOnState = new DeviceOnState();
- State refrigeratorOffState = new DeviceOffState();
- context.setState(refrigeratorOnState);
- context.execute();
- context.setState(refrigeratorOffState);
- context.execute();
- }
- }
package com.kb.state; public class Refrigerator { public static void main(String[] args) { DeviceContext context = new DeviceContext(); State refrigeratorOnState = new DeviceOnState(); State refrigeratorOffState = new DeviceOffState(); context.setState(refrigeratorOnState); context.execute(); context.setState(refrigeratorOffState); context.execute(); } }
We can see that using State pattern we got the same output without writing any if-else blocks
Now its not tightly coupled with if-else blocks
If we want to add any new state, we can easily add it without worrying about if-else blocks
Consider an example that, we need to add new state for the device called “Installing” state
Now we just need to create this state concrete class and use it in client program
Let’s see how can we do it
Step 1
Create a new state class for Installing state
- package com.kb.state;
- public class DeviceInstallingState implements State {
- @Override
- public void execute() {
- System.out.println("Device is Installing , Please do not turnOff");
- }
- }
package com.kb.state; public class DeviceInstallingState implements State { @Override public void execute() { System.out.println("Device is Installing , Please do not turnOff"); } }
Step 2
Use the same in the client program as below
- State refrigeratorInstallState = new DeviceInstallingState();
- context.setState(refrigeratorInstallState);
- context.execute();
State refrigeratorInstallState = new DeviceInstallingState(); context.setState(refrigeratorInstallState); context.execute();
We are just creating an instance of new state and attaching it to context class.
Whole client class with previous states is as below
- package com.kb.state;
- public class Refrigerator {
- public static void main(String[] args) {
- DeviceContext context = new DeviceContext();
- State refrigeratorOnState = new DeviceOnState();
- State refrigeratorOffState = new DeviceOffState();
- context.setState(refrigeratorOnState);
- context.execute();
- context.setState(refrigeratorOffState);
- context.execute();
- State refrigeratorInstallState = new DeviceInstallingState();
- context.setState(refrigeratorInstallState);
- context.execute();
- }
- }
package com.kb.state; public class Refrigerator { public static void main(String[] args) { DeviceContext context = new DeviceContext(); State refrigeratorOnState = new DeviceOnState(); State refrigeratorOffState = new DeviceOffState(); context.setState(refrigeratorOnState); context.execute(); context.setState(refrigeratorOffState); context.execute(); State refrigeratorInstallState = new DeviceInstallingState(); context.setState(refrigeratorInstallState); context.execute(); } }
Advantages of State pattern
Whenever we need to add new state for additional behaviour, its very easy to achieve.
Helps to avoid if-else (or) switch conditions in the program to execute different behaviours
Code is more robust and easy to maintain
When to use this pattern?
We should use this pattern whenever the change in the state of an object changes its behaviour.