Tuesday, July 12, 2022

Behavioral Design Patterns # State

State is a behavioral design pattern that lets an object alter its behavior when its internal state changes. It appears as if the object changed its class.

Problem

The State pattern is closely related to the concept of a Finite-State Machine.


The main idea is that, at any given moment, there’s a finite number of states which a program can be in. Within any unique state, the program behaves differently, and the program can be switched from one state to another instantaneously. 

However, depending on a current state, the program may or may not switch to certain other states. These switching rules, called transitions, are also finite and predetermined.

You can also apply this approach to objects. Imagine that we have a Document class. A document can be in one of three states: DraftModeration and Published. The publish method of the document works a little bit differently in each state:

  • In Draft, it moves the document to moderation.
  • In Moderation, it makes the document public, but only if the current user is an administrator.
  • In Published, it doesn’t do anything at all.


State machines are usually implemented with lots of conditional statements (if or switch) that select the appropriate behavior depending on the current state of the object. Usually, this “state” is just a set of values of the object’s fields. 

Even if you’ve never heard about finite-state machines before, you’ve probably implemented a state at least once. Does the following code structure ring a bell?

class Document is field state: string // ... method publish() is switch (state) "draft": state = "moderation" break "moderation": if (currentUser.role == 'admin') state = "published" break "published": // Do nothing. break // ...

The biggest weakness of a state machine based on conditionals reveals itself once we start adding more and more states and state-dependent behaviors to the Document class. 

Most methods will contain monstrous conditionals that pick the proper behavior of a method according to the current state.

Code like this is very difficult to maintain because any change to the transition logic may require changing state conditionals in every method.

The problem tends to get bigger as a project evolves. It’s quite difficult to predict all possible states and transitions at the design stage. 


Hence, a lean state machine built with a limited set of conditionals can grow into a bloated mess over time.



Solution


The State pattern suggests that you create new classes for all possible states of an object and extract all state-specific behaviors into these classes.

Instead of implementing all behaviors on its own, the original object, called context, stores a reference to one of the state objects that represents its current state, and delegates all the state-related work to that object.


To transition the context into another state, replace the active state object with another object that represents that new state. 

This is possible only if all state classes follow the same interface and the context itself works with these objects through that interface.

This structure may look similar to the Strategy pattern, but there’s one key difference. In the State pattern, the particular states may be aware of each other and initiate transitions from one state to another, whereas strategies almost never know about each other


Structure


If we have to change the behavior of an object based on its state, we can have a state variable in the Object. Then use if-else condition block to perform different actions based on the state.

State design pattern is used to provide a systematic and loosely coupled way to achieve this through Context and State implementations.

State Pattern Context is the class that has a State reference to one of the concrete implementations of the State. Context forwards the request to the state object for processing. Let’s understand this with a simple example.

Suppose we want to implement a TV Remote with a simple button to perform action. If the State is ON, it will turn on the TV and if state is OFF, it will turn off the TV.

We can implement it using if-else condition like below;
public class TVRemoteBasic { private String state=""; public void setState(String state){ this.state=state; } public void doAction(){ if(state.equalsIgnoreCase("ON")){ System.out.println("TV is turned ON"); }else if(state.equalsIgnoreCase("OFF")){ System.out.println("TV is turned OFF"); } } public static void main(String args[]){ TVRemoteBasic remote = new TVRemoteBasic(); remote.setState("ON"); remote.doAction(); remote.setState("OFF"); remote.doAction(); } }

Notice that client code should know the specific values to use for setting the state of remote. 
Further more if number of states increase then the tight coupling between implementation and the client code will be very hard to maintain and extend.
Now we will use State pattern to implement above TV Remote example.

State Design Pattern Interface

First of all we will create State interface that will define the method that should be implemented by different concrete states and context class.

public interface State { public void doAction(); }

State Design Pattern Concrete State Implementations

In our example, we can have two states – one for turning TV on and another to turn it off. So we will create two concrete state implementations for these behaviors.

public class TVStartState implements State { @Override public void doAction() { System.out.println("TV is turned ON"); } }

public class TVStopState implements State { @Override public void doAction() { System.out.println("TV is turned OFF"); } }

Now we are ready to implement our Context object that will change its behavior based on its internal state.

State Design Pattern Context Implementation

public class TVContext implements State { private State tvState; public void setState(State state) { this.tvState=state; } public State getState() { return this.tvState; } @Override public void doAction() { this.tvState.doAction(); } }

Notice that Context also implements State and keep a reference of its current state and forwards the request to the state implementation.

State Design Pattern Test Program

Now let’s write a simple program to test our state pattern implementation of TV Remote.

public class TVRemote { public static void main(String[] args) { TVContext context = new TVContext(); State tvStartState = new TVStartState(); State tvStopState = new TVStopState(); context.setState(tvStartState); context.doAction(); context.setState(tvStopState); context.doAction(); } }

You may also like

Kubernetes Microservices
Python AI/ML
Spring Framework Spring Boot
Core Java Java Coding Question
Maven AWS