Design Pattern Examples
Overview of object-oriented design patterns
State Pattern

Diagram of the State pattern

The State pattern is used when a finite state machine needs to be expressed in an object-oriented form.

A finite state machine is something that changes its behavior based on its state. It is a way to isolate specific behaviors from each other so that changes in one behavior doesn't affect other behaviors. There is also a performance benefit in that only a minimum amount of code is executed at any given time for any given state.

Here is a simple state machine, written in C:

/* Describes the current state of the state machine. */
{
InitState, /* Always transitions to State1 */
State1, /* Can transition to State1 or State2 */
State2, /* Can transition to State1 or EndState */
EndState /* Exit condition and transitions to InitState */
};
/*
Execute one step of the state machine, returning 1 if more state
needs to be executed; otherwise, returns 0 if processing is done
*/
int ProcessData(CurrentState* pState, void* pData)
{
int continueProcessing = 1;
switch (*pState):
{
case InitState:
DoInitialize(pData);
*pState = State1;
break;
case State1:
*pState = DoState1();
break;
case State2:
*pState = DoState2();
break;
case EndState:
DoEndState();
continueProcessing = 0;
*pState = InitState;
break;
}
return continueProcessing;
}
/* Execute the state machine on the given data, returning when done. */
void ExecuteStateMachine(void *pData)
{
CurrentState state = InitState;
while (ProcessData(&state, pData) != 0)
{
}
}
CurrentState
Represents the current state of the state machine.

For the above example, the details of DoInitialize(), DoState1(), DoState2() and DoEndState() don't matter except that DoState1() and DoState2() can change the state depending on some internal decision-making process. However, the overall flow should be clear that only one function is called for any given state.

The State design pattern describes a way to convert the above functional definition into an object-oriented one.

The CurrentState enumeration and the functions that do stuff for each state are encapsulated into classes. These state classes derive from a common base class or interface. The ProcessData() function is encapsulated in a context class that is passed to each state class (so the state class can operate on the data in the context). The driving force (that is, whatever makes the call to the state machine) is typically from outside the context class and will tend to look a lot like the ExecuteStateMachine() function in the above example, although the driving force could be anything from a timer to the user clicking a button.

As the state changes in the context class, a pointer to the current state class instance is changed to the next state class instance (as opposed to changing a simple integer or enumeration). There are two basic ways in which the state is changed:

  1. each state class knows what the next state class needs to be and returns an instance of that class
  2. The context maintains a dictionary of the state class instances and uses an enumeration such as CurrentState to look up the next state instance to use. Each state class then returns a value from the CurrentState to set the next state.

The second approach is better so that only one entity (the context class) knows about the instances of the state classes.

How to Use

Links to the State classes and interfaces or functions
C++ C# Python C
IStateContext interface IStateContext interface IStateContext interface <Not Applicable>
IStateBehavior interface IStateBehavior interface IStateBehavior interface <Not Applicable>
StateContext_Class class StateContext_Class class StateContext_Class StateContext structure
(Hidden in C++) <Not Applicable> StateContext_ClassImpl class State_RemoveComments()

The demonstration example parses a block of C, C++ or C# code (Python parses C++ code since Python does not have nested comments to show off) to remove all comments. It ignores comments inside of quotes. The context maintains an iterator through the text and an accumulator of comment-free text. The context also maintains the current state. The changes in state are displayed along the way.

This example also demonstrates a parser that does not use a look-ahead approach, where the next character coming is peeked at to determine which state should be next. The look-ahead can potentially simplify the number of states needed but the example would get quite messy with potentially multiple if statements for each state change.

Note: [C++, C#, Python] This example highlights one problem of an object-oriented approach to a state machine and that is the sheer number of small classes that need to be managed. You either get many classes in a single file or many files each with a small class. Contrast this with the C function approach, which is much simpler.

C++

void State_Exercise()
{
std::cout << std::endl;
std::cout << "State Exercise" << std::endl;
StateContext_Class filterContext;
std::string textToFilter =
"/*#################### Block Comment #################################*/\n"
"//#################### Line Comment ####################################\n"
"// A comment. /* A nested comment */\n"
"\n"
"void State_Exercise() // An exercise in state machines\n"
"{\n"
" char character = '\\\"';\n"
" std::cout << std::endl;\n"
" std::cout << \"\\\"State\\\" /*Exercise*/\" << std::endl;\n"
"\n"
" StateContext_Class filterContext;\n"
"\n"
" std::cout << \"\\t\\tDone. //(No, really)//\" << std::endl;\n"
"}";
std::cout << " Text to filter:" << std::endl;
std::cout << " Filtering text..." << std::endl;
std::string filteredText = filterContext.RemoveComments(textToFilter);
std::cout << " Filtered text:" << std::endl;
_State_DisplayText(filteredText);
std::cout << " Done." << std::endl;
}
static const char * textToFilter
static void _State_DisplayText(const char *textToDisplay)
Helper function to display text from the State exercise. Text is displayed with line numbers.

C#

public void Run()
{
Console.WriteLine();
Console.WriteLine("State Exercise");
StateContext_Class filterContext = new StateContext_Class();
string textToFilter =
"/*#################### Block Comment #################################*/\n" +
"//#################### Line Comment ####################################\n" +
"// A comment. /* A nested comment */\n" +
"\n" +
"void State_Exercise() // An exercise in state machines\n" +
"{\n" +
" char character = '\\\"';\n" +
" Console.WriteLine();\n" +
" Console.WriteLine(\"\\\"State\\\" /*Exercise*/\");\n" +
"\n" +
" StateContext_Class filterContext = new StateContext_Class();\n" +
"\n" +
" Console.WriteLine(\"\\t\\tDone. //(No, really)//\");\n" +
"}";
Console.WriteLine(" Text to filter:");
Console.WriteLine(" Filtering text...");
string filteredText = filterContext.RemoveComments(textToFilter);
Console.WriteLine(" Filtered text:");
_State_DisplayText(filteredText);
Console.WriteLine(" Done.");
}

Python

def State_Exercise():
print()
print("State Exercise")
filterContext = StateContext_Class()
textToFilter = \
'''/*#################### Block Comment
//
// A comment. /* A nested comment */
void State_Exercise() // An exercise in state machines
{
char character = '\\"';
std::cout << std::endl;
std::cout << "\\"State\\" /*Exercise*/" << std::endl;
StateContext_Class filterContext;
std::cout << "\\t\\tDone. //(No, really)//" << std::endl;
}'''
print(" Text to filter:")
_State_DisplayText(textToFilter)
print(" Filtering text...")
filteredText = filterContext.RemoveComments(textToFilter)
print(" Filtered text:")
_State_DisplayText(filteredText)
print(" Done.")

C

void State_Exercise(void)
{
printf("\nState_Exercise\n");
printf(" Text to filter:\n");
printf(" Filtering text...\n");
DynamicString filteredText = { 0 };
bool success = State_RemoveComments(textToFilter, &filteredText);
if (success)
{
printf(" Filtered text:\n");
_State_DisplayText(filteredText.string);
}
DynamicString_Clear(&filteredText);
printf(" Done.\n");
}
bool State_RemoveComments(const char *text, DynamicString *filteredText)
Entry point for callers to filter text. Removes C++-style line and block comments from the text.
void DynamicString_Clear(DynamicString *string)
Clear a DynamicString object, releasing any allocated memory. Resets to an empty string.
Definition: dynamicstring.c:27
Represents a string that can be grown dynamically.
Definition: dynamicstring.h:16
char * string
The string that can grow.
Definition: dynamicstring.h:17

RUST

(Apologies. Doxygen does not understand Rust syntax and therefore cannot colorize the code.)

pub fn state_exercise() -> Result<(), String> {
println!("");
println!("State Exercise");
let mut context = StateContext::new();
let text_to_filter =
r#"/*#################### Block Comment #################################*/
//#################### Line Comment ####################################
// A comment. /* A nested comment */
fn state_exercise() { // An exercise in state machines
let character = '\"';
println!("");
println!("\"State\" /*Exercise*/");
let mut context = StateContext::new();
println!("\t\tDone. //(No, really)//");
}"#;
println!(" Text to filter:");
state_display_text(text_to_filter);
println!(" Filtering text...");
let filtered_text = context.remove_comments(text_to_filter);
println!(" Filtered text:");
state_display_text(&filtered_text);
println!(" Done.");
Ok(())
}

See Also