"The state design pattern allows for full encapsulation of an unlimited number of states on a context for easy maintenance and flexibility."
Table of content
Definition
The State Design Pattern enables the context to behave differently based on the currently active ConcreteState
instance. Let us explain it with the following class diagram.
Context object
The Context
is a class that contains a variable state. In fact, it could have many different state variables. The context can change from one to another state.
The Context
object:
- has at least one method to process requests and passes these requests along to the state objects for processing.
- has no clue on what the possible states are.
- must not be aware of the meaning of these different states.
- does not do any manipulation of the states (no state changes).
- may set an initial state at startup and therefore must be aware of the existence of that initial state.
The main advantage of not knowing what states the context could be in is that you can add as many new states as required over time. This makes maintaining the context super simple and flexible.
State
The State
class is usually an abstract class and not an interface. This class is the base class for all possible states. The reason why this class is usually an abstract class and not an interface is because there are usually common actions required to apply to all states. These global methods can be implemented in this base class.
The State
class defines all possible method signatures that all states must implement. This is extremely important to keep the maintenance of all possible states as simple as possible. Since all states will implement these methods signatures and if you forget to implement a new method, the compiler will warn you at compile time.
ConcreteState
The ConcreteState
object implements the actual state behavior for the context object.
- It inherits from the base
State
class. - It must implement all methods from the abstract base
State
class. - It has all the business knowledge required to make decisions about its state behavior. It makes decisions on when and how it should switch from one state to another.
- It has knowledge of other possible
ConcreteState
objects so that it can switch to another state if required. - It can even check other context objects and their states to make business decisions. Many times, an object may have more than one context object. When this happens, a
ConcreteState
object may need to access these different states and make a decision based on active states. - It also is capable of handling before and after transitioning to states. Being aware of a transition about to happen is an extremely powerful feature. For example, this can be used for logging, audit recording, security, firing off external services, kicking of workflows, etc. and many other purposes.
- It allows the full use of a programming language when compared to state machines. Nothing is more powerful in abstract logic and conditionals coupled with object orientation as a computer programming language compared to state machines and their implementations.
As you add new methods to the abstract base class over time, each ConcreteState
class will need to implement that method. This forces you to think from the point of view of the current state. Moreover, you know exactly where to go to maintain that code. Maintainability is king in software development.
Where we use it ?
We use it when the behavior of an object has several states, and the class has to change its behavior at runtime according to its state; or when we have several conditions in the source code.
Example
The example is a piece of Software which controls the state execution of movements (Sequence) of any instrument of the Gran Telescopio CANARIAS (GTC). More specifically, the Sequence executes high-level operations related to any component that participates during one observation.
In the instruments EMIR or FRIDA, the Sequence can be in different states, such as running, paused, aborted. More specifically, the Sequence can be in the following states:
Undefined
: the input parameters of the Sequence are not defined.Defined
: the input parameters of the Sequence are defined.Running
: the Sequence is executing. The execution changes the current values of all the elements that participate in the Sequence.Finished
: the Sequence ends is execution.Paused
: the user paused the execution of the Sequence. The values of all the elements that participate in the Sequence are stored.Aborted
: the user terminates the execution. It stablishes the Sequence to theUndefined
state.Fault
: there is an error in the Sequence execution.
The operation of the Sequence depends on the different individual states. The more states, the more complicated it would be to maintain this using traditional Switch
or If
statements. We could use a state machine as well but it will not buy you the flexibility and ease of use when compared to the State Design Pattern. Please, feel free to add new states and try to experiment with it.
The transitions of our states are shown in the next picture :
As we can see, we have nine transitions: define(), reset(), execute(), resume(), abort(), finish(), pause(), restore() and fault(). Some transitions are like synonims of other transitions, such as, the transition restore() which calls the procedure define(), and the transition resume() which calls the method execute().
The class diagram is shown in the next picture:
The Sequence
class is the context object to state's operations. The state is represented with the SeqState
class. The different kinds of states inherit from the class SeqState
. For instance, the Undefined
and Defined
classes are two of the ConcreteClass
implementations of the State Design Pattern. Let's take a closer look at how the classes Sequence
, SeqState
, Undefined
, and Defined
are implemented:
Sequence.h
/*
* Created on: 8 feb. 2018
* Author: Cesar Augusto Guzman Alvarez
* All Rights Reserved
*/
#ifndef L_Sequence_H_
#define L_Sequence_H_
// Includes
#include <string>
#include <iostream>
#include "SeqState.h"
using namespace std;
/**
* The aim of this class is to represent an entity through which the sequence can be controlled according to its state
*/
class Sequence {
//-----------------------------------------------------------------
// private section
//-----------------------------------------------------------------
private:
// name of the sequence
string name_;
// current state of the Sequence
SeqState* currentState_;
//--------------------------------------------------
// public section
//--------------------------------------------------
public:
//########################## CONSTRUCTOR #########################
Sequence();
//########################## METHODS TO TRANSICT ############################
void define();
void reset();
//########################## GETTERS AND SETTERS ############################
void setState(SeqState* state);
string getState();
};
#endif // L_Sequence_H_
Sequence.cpp
/*
* Created on: 8 feb. 2018
* Author: Cesar Augusto Guzman Alvarez
* All Rights Reserved
*/
#pragma hdrstop
// Includes
#include "Sequence.h"
#include "Undefined.h"
//######################## CONSTRUCTOR #######################
//----------------------------------------------------------------------
// Constructor
//----------------------------------------------------------------------
Sequence::Sequence() {
currentState_ = new Undefined();
}
//########################## SETTERS #######################################
//----------------------------------------------------------------------
// Change the current state
//----------------------------------------------------------------------
void Sequence::setState ( SeqState* state) {
currentState_ = state;
}
//########################## METHODS TO TRANSIT ############################
//----------------------------------------------------------------------
// GO TO STATE DEFINE
//----------------------------------------------------------------------
void Sequence::define() {
currentState_->define(this);
}
//----------------------------------------------------------------------
// GO TO STATE reset
//----------------------------------------------------------------------
void Sequence::reset() {
currentState_->reset(this);
}
#pragma package(smart_init)
SeqState.h
/*
* Created on: 8 feb. 2018
* Author: Cesar Augusto Guzman Alvarez
* All Rights Reserved
*/
#ifndef L_SequenceState_H_
#define L_SequenceState_H_
// forward declaration
class Sequence;
class SeqState {
//--------------------------------------------------
// public section
//--------------------------------------------------
public:
//########################## METHODS TO TRANSICT ############################
virtual void define(Sequence* sequence);
virtual void reset(Sequence* sequence);
//########################## GETTERS ############################
virtual std::string getState();
protected:
std::string name_;
};
#endif // L_SequenceState_H_
SeqState.cpp
/*
* Created on: 8 feb. 2018
* Author: Cesar Augusto Guzman Alvarez
* All Rights Reserved
*/
#pragma hdrstop
// Includes
#include <iostream>
#include "SeqState.h"
using namespace std;
//########################## METHODS TO TRANSICT ############################
void SeqState::define(Sequence* sequence) {
cout << "Already Defined\n";
}
void SeqState::reset(Sequence* sequence) {
cout << "Already Undefined\n";
}
//########################## GETTERS ############################
string SeqState::getState() {
return name_;
}
#pragma package(smart_init)
Undefined.h
/*
* Created on: 8 feb. 2018
* Author: Cesar Augusto Guzman Alvarez
* All Rights Reserved
*/
#ifndef L_Undefined_H_
#define L_Undefined_H_
#include <iostream>
#include <stdio.h>
#include "SeqState.h"
#include "Sequence.h"
class Undefined : public SeqState {
//--------------------------------------------------
// public section
//--------------------------------------------------
public:
//########################## CONSTRUCTOR #########################
Undefined();
//########################## METHODS TO TRANSICT ############################
void define(Sequence* context);
};
#endif /* L_Undefined_H_ */
Undefined.cpp
/*
* Created on: 8 feb. 2018
* Author: Cesar Augusto Guzman Alvarez
* All Rights Reserved
*/
#pragma hdrstop
#include "Undefined.h"
#include "SeqStateDefined.h"
//########################## CONSTRUCTOR #########################
Undefined::Undefined() {
name_ = "Undefined";
}
//########################## METHODS TO TRANSICT ############################
void Undefined::define ( Sequence* context) {
printf("Go to state Defined\n");
SeqState* nextState = new SeqStateDefined();
context->setState(nextState);
delete this;
}
#pragma package(smart_init)
Defined.h
/*
* Created on: 8 feb. 2018
* Author: Cesar Augusto Guzman Alvarez
* All Rights Reserved
*/
#ifndef L_Defined_H_
#define L_Defined_H_
#include <iostream>
#include <stdio.h>
#include "SeqState.h"
#include "Sequence.h"
class Defined : public SeqState {
//--------------------------------------------------
// public section
//--------------------------------------------------
public:
//########################## CONSTRUCTOR #########################
Defined();
//########################## METHODS TO TRANSICT ############################
void reset(Sequence* sequence);
};
#endif /* L_Defined_H_ */
Defined.cpp
/*
* Created on: 8 feb. 2018
* Author: Cesar Augusto Guzman Alvarez
* All Rights Reserved
*/
#pragma hdrstop
#include "Defined.h"
#include "Undefined.h"
//########################## CONSTRUCTOR #########################
Defined::Defined() {
name_ = "Defined";
}
//########################## METHODS TO TRANSICT ############################
void Defined::reset(Sequence* context) {
printf("Go to state Undefined\n");
SeqState* nextState = new Undefined();
context->setState(nextState);
delete this;
}
#pragma package(smart_init)
TestSequence.h
/*
* Created on: 8 feb. 2018
* Author: Cesar Augusto Guzman Alvarez
* All Rights Reserved
*/
#pragma hdrstop
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <vector>
#include <assert.h>
#include "Sequence.h"
#include "SeqState.h"
using namespace std;
class TestSequence: public Sequence {
//--------------------------------------------------
// public section
//--------------------------------------------------
public:
enum SeqStateType {
DEFINED, UNDEFINED
};
vector statesToTest;
//################### CONSTRUCT ###################
TestSequence();
//################### METHODS OF TRANSITIONS TO TEST ###################
void Defined();
void Undefined();
//################### METHODS TO EXECUTE THE TEST ###################
void TestTransition();
//--------------------------------------------------
// private section
//--------------------------------------------------
private:
//################### METHODS OF TRANSITIONS TO TEST ###################
void TestDefined();
void TestUndefined();
//################### METHODS TO EXECUTE ONE TEST ###################
void TestState(SeqStateType typeState);
};
TestSequence.cpp
/*
* Created on: 8 feb. 2018
* Author: Cesar Augusto Guzman Alvarez
* All Rights Reserved
*/
#pragma hdrstop
#include "TestSequence.h"
//################### CONSTRUCT ###################
TestSequence::TestSequence () {
}
//################### METHODS TO EXECUTE THE TEST ###################
void TestSequence::TestTransition() {
for (unsigned i = 0; i < statesToTest.size(); i++)
TestState(statesToTest[i]);
}
//################### METHODS TO EXECUTE ONE TEST ###################
void TestSequence::TestState(SeqStateType typeState) {
switch(typeState) {
case DEFINED: TestDefined(); break;
case UNDEFINED: TestUndefined();break;
}
}
//################### METHODS OF TRANSITIONS TO TEST ###################
void TestSequence::Defined() {
printf("defined");
statesToTest.push_back(DEFINED);
}
void TestSequence::Undefined() {
printf("undefined");
statesToTest.push_back(UNDEFINED);
}
void TestSequence::TestDefined() {
this.define();
assert(strcmp(this.getState().c_str(), "Defined")==0);
}
void TestSequence::TestUndefined() {
this.reset();
assert(strcmp(this.getState().c_str(), "Undefined")==0);
}
main.cpp
/*
* Created on: 8 feb. 2018
* Author: Cesar Augusto Guzman Alvarez
* All Rights Reserved
*/
#pragma hdrstop
#include <iostream>
#include <stdio.h>
#include "TestSequence.h"
int main(int argc, char* argv[])
{
TestSequence* sequence = new TestSequence();
printf("*****************");
printf("\nTest transitions");
printf("\n*****************");
printf("\n1. ");
sequence.Defined();
printf(" -> ");
sequence.Undefined();
printf(" -> ");
sequence.Undefined();
printf("\n\n");
sequence.TestTransition();
delete sequence;
return 0;
}
The ouput of executing the main program is:
***************** Test transitions ***************** 1. defined -> undefined -> undefined Go to state Defined Go to state Undefined Already Undefined
Please add a comment to any question related about the previous code ;)
Hierarchical State Design Pattern
The previous example describes the basic states to be used for any instrument in GTC. However, some instruments may require to extends the finite state machine. For instance, the instrument Phaser, which is a coordinator component that cophases the primary mirror of GTC through the coordination of a large number of Telescope subsystems, requires one more state, WaitForReduction
, in which the instrument Phaser is waitting that a third component make a reduction in the current took image.
Considering this situation, the Sequence state machine and the class diagram will be as follows:
In the state machine, we add a new state WaitForReduction
and a new transition called reduce(), so, from the state Running
the state machine can transit to the state WairForReduction
by calling the operator reduce().
In the class diagram, we created a new context object SequencePhaser
that extends the context Sequence
to add a new method reduce(), which creates the transition to the state WaitForReduction
. The class WaitForReduction
inherits of an abstract state SeqStatePhaser
that creates a need transition reduce().
The source code is as follows:
SequencePhaser.h
/*
* Created on: 14 feb. 2018
* Author: Cesar Augusto Guzman Alvarez
* All Rights Reserved
*/
#ifndef L_SequencePhaser_H_
#define L_SequencePhaser_H_
// Includes
#include <string>
#include <iostream>
#include <stdio.h>
#include <string.h>
#include "Sequence.h"
using namespace std;
/**
* The aim of this class is to represent an entity through which the sequence of the Phaser can be controlled according to its state
*/
class SequencePhaser : public Sequence {
//--------------------------------------------------
// public section
//--------------------------------------------------
public:
//########################## CONSTRUCTOR & DESTRUCTOR #########################
SequencePhaser();
//########################## METHODS TO TRANSICT ############################
void reduce();
};
#endif // L_SequencePhaser_H_
SequencePhaser.cpp
/*
* Created on: 14 feb. 2018
* Author: Cesar Augusto Guzman Alvarez
* All Rights Reserved
*/
#pragma hdrstop
// Includes
#include "SequencePhaser.h"
#include "WaitForReduction.h"
//######################## CONSTRUCTOR #######################
//----------------------------------------------------------------------
// Constructor
//----------------------------------------------------------------------
SequencePhaser::SequencePhaser() {
}
//########################## METHODS TO TRANSIT ############################
//----------------------------------------------------------------------
// Go to reduce state
//----------------------------------------------------------------------
void SequencePhaser::reduce() {
string currentState = currentState_->getState();
// if the current state is Running
if (strcmp(currentState.c_str(), "Running")==0) {
SeqState* nextState = new WaitForReduction();
printf("Go to state %s\n", nextState->getState().c_str());
delete currentState_;
setState(nextState);
} else {
printf("No possible transition!");
}
}
#pragma package(smart_init)
SeqStatePhaser.h
/*
* Created on: 14 feb. 2018
* Author: Cesar Augusto Guzman Alvarez
* All Rights Reserved
*/
#ifndef L_SeqStatePhaser_H_
#define L_SeqStatePhaser_H_
#include "SeqState.h"
// forward declaration
class Sequence;
class SeqStatePhaser : public SeqState {
//--------------------------------------------------
// public section
//--------------------------------------------------
public:
//########################## METHODS TO TRANSICT ############################
virtual void reduce(Sequence* sequence);
};
#endif // L_SeqStatePhaser_H_
SeqStatePhaser.cpp
/*
* Created on: 14 feb. 2018
* Author: Cesar Augusto Guzman Alvarez
* All Rights Reserved
*/
#pragma hdrstop
// Includes
#include <iostream>
#include "SeqStatePhaser.h"
using namespace std;
//########################## METHODS TO TRANSICT ############################
void SeqStatePhaser::reduce(Sequence* sequence) {
cout << "Already "<<name_<<"\n";
}
#pragma package(smart_init)
WaitForReduction.h
/*
* Created on: 14 feb. 2018
* Author: Cesar Augusto Guzman Alvarez
* All Rights Reserved
*/
#ifndef L_WaitForReduction_H_
#define L_WaitForReduction_H_
#include <iostream>
#include <stdio.h>
#include "SeqStatePhaser.h"
#include "Sequence.h"
class WaitForReduction : public SeqStatePhaser {
//--------------------------------------------------
// public section
//--------------------------------------------------
public:
//########################## CONSTRUCTOR & DESTRUCTOR #########################
WaitForReduction();
//########################## METHODS TO TRANSICT #########################
void define(Sequence* sequence);
void reset(Sequence* sequence);
void execute(Sequence* context);
void resume(Sequence* context);
void abort(Sequence* context);
void finish(Sequence* context);
void pause(Sequence* context);
void fault(Sequence* context);
void restore(Sequence* context);
};
#endif /* L_WaitForReduction_H_ */
WaitForReduction.cpp
/*
* Created on: 14 feb. 2018
* Author: Cesar Augusto Guzman Alvarez
* All Rights Reserved
*/
#pragma hdrstop
#include "WaitForReduction.h"
#include "Undefined.h"
#include "SeqStateRunning.h"
//########################## CONSTRUCTOR & DESTRUCTOR #########################
WaitForReduction::WaitForReduction() {
name_ = "WaitForReduction";
}
//########################## METHODS TO TRANSICT #########################
void WaitForReduction::reset(Sequence* context) {
printf("No possible transition!\n");
}
void WaitForReduction::define(Sequence* context) {
printf("No possible transition!\n");
}
void WaitForReduction::resume(Sequence* context) {
execute(context);
}
void WaitForReduction::execute(Sequence* context) {
SeqState* nextState = new SeqStateRunning();
printf("Go to state %s\n", nextState->getState().c_str());
context->setState(nextState);
delete this;
}
void WaitForReduction::abort(Sequence* context) {
printf("No possible transition!\n");
}
void WaitForReduction::finish(Sequence* context) {
printf("No possible transition!\n");
}
void WaitForReduction::pause(Sequence* context) {
printf("No possible transition!\n");
}
void WaitForReduction::restore(Sequence* context) {
printf("No possible transition!\n");
}
void WaitForReduction::fault(Sequence* context) {
printf("No possible transition!\n");
}
#pragma package(smart_init)
main.cpp
/*
* Created on: 14 feb. 2018
* Author: Cesar Augusto Guzman Alvarez
* All Rights Reserved
*/
#pragma hdrstop
#include <iostream>
#include <stdio.h>
#include "TestSequencePhaser.h"
//---------------------------------------------------------------------------
int main(int argc, char* argv[])
{
TestSequencePhaser* sequencePhaser = new TestSequencePhaser();
printf("\n\n*****************");
printf("\nTest transitions phaser");
printf("\n*****************");
printf("\ntransition 1: ");
sequencePhaser->Defined();
printf(" -> ");
sequencePhaser->Running();
printf(" -> ");
sequencePhaser->WaitForReduction();
printf(" -> ");
sequencePhaser->Running();
printf("\n\n");
sequencePhaser->TestTransition();
delete sequencePhaser
;
return 0;
}
The output of executing the main program is as follows:
*****************
Test transitions phaser
*****************
transition 1: defined -> running -> wait for reduction -> running
Go to state Defined
Go to state Running
Go to state WaitForReduction
Go to state Running
Notice that the class WaitForReduction
implements all the transitions of the state machine that were not implemented in the first example. It is also important to note that the class TestSequencePhaser
needs to be implemented according to the class TestSequence
History, having memory
The problem is that finite state machines have no concept of history. You know what state you are in, but have no memory of what state you were in. There’s no easy way to go back to a previous state. In our example, this is not happen in the sense of the states but in the values that our instrument hat at the moment to transit from the state Running
to the state Paused
. However, we can keep an history of the state transitions to perform, later, a trace of the execution. In order to do it we may use in the context object a variable stack<string> historyStates_; that save the previous visited states. In order to save more elements of the instrument, we can change the <string> for an specific Object that contains the previous visited state and all other elements to save.
Advantages
- The benefits of implementing polymorphic behavior are evident.
- It is easier to add states to support additional behavior.
- This removes the dependency on the if/else or switch/case conditional logic because an object’s behavior is the result of the function of its state, and the behavior gets changed at runtime depending on the state.
- It improves Cohesion since state-specific behaviors are aggregated into the
ConcreteState
classes, which are placed in one location in the code.
Finite state machines are useful when:
-
You have an entity whose behavior changes based on some internal state.
-
That state can be rigidly divided into one of a relatively small number of distinct options.
-
The entity responds to a series of inputs or events over time.
Disadvantages
- The number of classes grows up.
References
https://www.codeproject.com/Articles/509234/The-State-Design-Pattern-vs-State-Machine accessed on 06/02/2018
https://www.geeksforgeeks.org/state-design-pattern/ accessed on 06/02/2018
http://gameprogrammingpatterns.com/state.html accessed on 12/02/2018
This is a totally educational post.
Please write comments if you find anything incorrect, or you want to share more information about the topic discussed above.