The state pattern

In a previous post I talked about how we could modify a software’s behavior by using object composition (as opposed to class inheritance). A clear example is the state pattern. Let’s take a look.

The problem

So given the following code:

Class Car{

Bool isOn;

double velocity;
double gas;

Public void TurnOn(){
if(!isOn) isOn = true;
}

Public void Accelerate(){
if(!isOn) return;
if(gas<1) return;
velocity+=5;
gas-=2.5;
}

Public void TurnRadioOn(){
if(isOn) …
}

}

So, what’s wrong with this code? Well, the problem is that for every operation we add, we must check if the car is on or off. If you keep on adding states like no gas, you will end with a lot of flags and conditional logic based on that. And, mark my words, it’ll become a bug’s lair and a complicate piece to maintain.

Whenever you find code like this, congratulations, you have found yourself a state machine.

Refactoring to a state machine

A state machine is a way of reasoning that simplifies reasoning about a program by identifying the possible states the software can take at any given moment and the transitions between them. In our example a car can be in an off or on state. If you try to accelerate and the car is off, nothing will happen, however if it’s on, it will increase its speed. To refactor the code to a state machine you need to identify the states, extract the associated behavior to a state on an object and invoke the logic on the state object methods.

Identify the application states

The easiest way to identify the application states is to look for the conditional logic on the application, especially those based on a Boolean flag. In our case let’s suppose that we have the following:

Extract the state associated behavior to an object of its own

Now we must create objects that represent the behavior for each state of the application. Since the operations for each state are the same, we can create an interface.

Interface CarState {
CarState Accelerate(ref double velocity, ref double gas);
void TurnRadioOn();
CarState TurnOn();
}

class CarOff : CarState{

Public CarState Accelerate(ref double velocity, ref double gas){
return this;

}

Public void TurnRadioOn(){
//do nothing
}

Public CarState TurnOn(){
return new CarOn();
}
}

class CarOn : CarState {

Public CarState Accelerate(ref int velocity, ref int gas){
velocity +=5;
gas -=2.5;
if(gas <1)
return new NoGas();
else
return this;
}

Public void TurnRadioOn(){
//turn on the radio
}

Public void TurnOn(){
return this;
}
}

class NoGas {

Public CarState Accelerate(ref int velocity, ref int gas){
return this;
}

Public void TurnRadioOn(){
//do nothing
}
Public CarState TurnOn(){
return this;
}

}

Invoke the logic in the state object methods

Now let’s delegate the state behavior to the state objects:

Class Car{

double velocity;
double gas;

CarState state = new CarOff();

Public void TurnOn(){
state = state.TurnOn();
}

Public void Accelerate(){
state = state.Accelerate( ref velocity, ref gas);
}

Public void TurnRadioOn(){
state.TurnRadioOn();
}

}

Now the Car object is simple to maintain and understand.

When to use the state pattern

  1. Whenever you find yourself looking to a lot of conditions based on booleans, pay attention, you are probably looking to a state machine type of problem. If you have more than 2 states, I strongly suggest that you consider refactoring to the state pattern.
  2.  There are situations when you must evaluate several variables at once, like:
    If(!isOn & gas > 0 & battery >0 ) then …

    Refactor those expressions into Boolean values:

    bool carBroken = !isOn & gas > 0 & battery >0;

    And model your object behavior as a state machine, just like we outlined before.

Closing thoughts

Keep in mind that this example is for illustration purposes only. In real life, this is likely to be way more complicated.

Remember that there is a price to pay for using any design pattern. In this case the flexibility and simplification required the creation of more objects. Always weigh the pros and cons before coding anything!

As always, let me know your thoughts.