Understanding State Pattern in C++
A very simple way to understand State Design Pattern
Introduction
State pattern is a behavioral type design pattern which is widely used in different applications especially in 3D-Graphics applications and applications for devices. In object oriented design, object can change its behavior based on its current state. A state pattern design implements state and behavior of an object. By using state pattern, we can reduce the complexity in handling different states of an object. This will help us to easily maintain code in future.
Legacy Method of Handling State of an Object
There are a lot of applications which need to maintain different states on different contexts. Mainly, it comes in the matter of 3D-Applications and devices. Without using state pattern, normally we are handling the state as follows:
class MyDevice
{
int m_nCurrentState;
public:
MyDevice()
{
m_nCurrentState= STATE_OFF;
}
// Function handling the state changes
void HandleState();
};
void MyDevice::HandleState()
{
/* changing state depends on the current state */
if(STATE_OFF == m_nCurrentState)
{
m_nCurrentState = STATE_ON;
}
else if(STATE_ON == m_nCurrentState)
{
m_nCurrentState = STATE_ACQUIRING;
}
else if(STATE_ACQUIRING == m_nCurrentState)
{
m_nCurrentState = STATE_SAVE;
}
....
....
// We can add more states here
}
The problem with the above code is, usually the state changing and other activities will be centralized, so that the main function doing these activities will get complex and lengthy if there are many states are to be handled. This function will also get complex in the matter of maintainability. For handling few number of states, this approach is most suitable and easy to handle. The state pattern comes when complexity matters.
The Concept Behind the State Pattern
In state pattern, an object can have different states. Each state knows what its next state is. By using this architecture, we can add or remove a state at ease. This architecture mainly consists of 3 types of classes:
Context
Class: This class is the one which has the states. The application will be using (interfering with) this class. The class is responsible to maintain current state.State
Class: This is anabstract
base class which can hold the different states (sub classes). The context class will be using this class pointer as its member to point the current state.Concentrate
Class: This class implements the behavior of the state and behavior of the object.
Example: State Change
Here I’m taking the Sun as example: A person who is living in earth can view the sun in different states (even it remains constant). Those are morning, noon, afternoon, evening, night. For the sake of simplicity, I’m taking 3 states: Morning, Evening and Night.
The Sun starts (Initial state) with state Morning.
Morning knows that sun will move to Evening after its time period, Evening knows that beautiful Night will come after its turn and finally after a dark Night, it knows that Sun will change to a cool Morning. In this architecture, each state has a next state. When the object receives a command to change its state, it will ask the current state, what its next state is and modify itself to the new state returned.
Adding a New State
Suppose we want to add a new state Noon for Sun. We can do in simple steps as described below. Take Noon
as an example:
- Define a new concentrate class (
Noon
class) - Define its next state (
Evening
) - Give this state as the next state of another state (change
Morning
’s next state fromEvening
toNoon
).
Code
State Class
class CBaseState
{
public:
// Pure virtual function
virtual CBaseState* GetNextState() = 0;
// print the string
virtual char* ToString() = 0;
};
Concentrate Classes
//////////////////////////////////////////////////////////////////////////
// State Morning
//////////////////////////////////////////////////////////////////////////
class CMorning : public CBaseState
{
public:
virtual CBaseState* GetNextState();
virtual char* ToString();
};
//////////////////////////////////////////////////////////////////////////
// State Evening
//////////////////////////////////////////////////////////////////////////
class CEvening : public CBaseState
{
public:
virtual CBaseState* GetNextState();
virtual char* ToString();
};
//////////////////////////////////////////////////////////////////////////
// State night
//////////////////////////////////////////////////////////////////////////
class CNight: public CBaseState
{
public:
virtual CBaseState* GetNextState();
virtual char* ToString();
};
Context Class
//////////////////////////////////////////////////////////////////////////
// Context Class
//////////////////////////////////////////////////////////////////////////
class CSun
{
public:
CSun();
CSun(CBaseState* pContext /* Pass Allocated memory */);
~CSun();
// Handles the next state
void StateChanged();
char* GetStateName();
protected:
void DoCleanUp();
// Pointer which holds the current state
// Since this is and base class pointer
// of Concentrate classes, it can holds their objects
CBaseState* m_pState;
};
When State Change Request Comes
In the above example, the sun will be initialized to any of the states, for e.g., say morning
.
CSun objSun(new CMorning);
If we need to change its current state to the next state, it is possible to do so by calling StateChanged
interface provided by the context
class.
See the sample code snippet for initializing and changing the state:
CSun objSun(new CMorning);
printf("\n\nSun Says Good %s !!!",objSun.GetStateName());
// inform that state has been changed
objSun.StateChanged();
printf("\n\nSun Says Good %s !!!",objSun.GetStateName());
// inform that state has been changed
objSun.StateChanged();
printf("\n\nSun Says Good %s !!!",objSun.GetStateName());
// inform that state has been changed
objSun.StateChanged();
printf("\n\nSun Says Good %s !!!",objSun.GetStateName());
Inside the interface, the following things are happening:
// Handles the state change
void CSun::StateChanged()
{
if (m_pState)
{
// Getting Next State
CBaseState* pState = m_pState->GetNextState();
// de allocates the memory
delete m_pState;
m_pState = pState;
}
}
Defining Next State
Calling the GetNextState
virtual function depends on the object. Each object will return its next state.
Morning
object’s next state:
CBaseState* CMorning::GetNextState()
{
return new CEvening;
}
Evening
object’s next state:
CBaseState* CEvening::GetNextState()
{
return new CNight;
}
Night
object’s next state:
CBaseState* CNight::GetNextState()
{
return new CMorning;
}
What About This Code
The above described code is a very basic level implementation of state Pattern. You can add more interfaces for your class to manage the state (e.g. setting and getting state) and improve the code with a good memory handling strategy.