"The state design pattern allows for full encapsulation of an unlimited number of states on a context for easy maintenance and flexibility."

 

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.

StateDesign

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 the Undefined 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 :

FridaSequenceFSM

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:

FridaDSSequence

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:

FridaSequenceFSMPhaser

FridaDSSequencePhaser

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.

Pin It

Add comment


Security code
Refresh