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:
{
InitState,
State1,
State2,
EndState
};
{
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;
}
void ExecuteStateMachine(void *pData)
{
while (ProcessData(&state, pData) != 0)
{
}
}
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:
- each state class knows what the next state class needs to be and returns an instance of that class
- 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
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;
"/*#################### 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;
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();
"/*#################### 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:");
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:")
print(" Filtering text...")
filteredText = filterContext.RemoveComments(textToFilter)
print(" Filtered text:")
print(" Done.")
C
void State_Exercise(void)
{
printf("\nState_Exercise\n");
printf(" Text to filter:\n");
printf(" Filtering text...\n");
if (success)
{
printf(" Filtered text:\n");
}
printf(" Done.\n");
}
void DynamicString_Clear(DynamicString *string)
Clear a DynamicString object, releasing any allocated memory. Resets to an empty string.
Represents a string that can be grown dynamically.
char * string
The string that can grow.
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