Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Object-Oriented Finite-State Machines


February 1998/Object-Oriented Finite-State Machines

Object-Oriented Finite-State Machines

Frantisek Kaduch, Damian Jan, and Purificacion Vidal

Finite-state machines occur all over the place. A reusable base class can capture code that's common to many FSM applications.


We present an object-oriented, yet simple and practical, implementation of a finite-state machine (FSM). We designed it as a plug-in component, ready to use and adaptable to a particular application by altering just one derived class.

FSM is a modelling concept used in such varied technical fields as telecommunications protocols, telephone calls processing, digital hardware design, virtual memory management in operating systems, and so forth [1]. But its software implementations tend to be complex and offer small code reuse. To achieve greater reuse here, we employed a bridge design pattern. The principal classes remain unchanged, while application-specific behavior is introduced through inheritance.

We developed this implementation as a part of an OSI communications stack software package. We have reused it successfully in call-processing software based on Dialogic voice processing boards. It is portable across various operating systems. Since it does not use very recent C++ features, it should compile with any standard C++ compiler.

An FSM implementation tends to be complex, with little opportunity for reuse, because it is so intimately entwined with application-specific behavior. To make matters worse, a classic FSM implementation is often sprinkled throughout the program, making it difficult to find a clear boundary between the functionality of the application and the FSM itself. A programmer who inherits this kind of program and has to modify the FSM behaviour — to add new transitions, for example — faces two options:

1) To dig through existing code and figure out how it works.
2) To rewrite it from scratch.

The second alternative is particularly tempting if a design is too complex, with a lot of states, events, and transitions.

By contrast, maintenance is quite easy with our approach. The FSM core is placed in the initialization part of a main function. You specify the FSM here and then just call one control function to process an event. We needed the FSM core in the form of an object-oriented software component for several applications based on the OSI protocol state machines. The result of our effort was a plug-in component, usable in different applications.

It is a simple and practical implementation. The FSM has states, events, and transitions. When an event is received in a source state, a transition is triggered and a set of associated actions is executed. The FSM then passes to the destination state, or remains in the current one and waits for another event.

We avoid the use of switch statements. We feel they obscure the flow of program execution and make debugging more difficult. At the same time, we paid careful attention to run-time efficiency and small size, as we use the FSM code often in real-time systems. And, of course, we strove for portability across different operating systems and C++ compilers.

Design of the FSM

The design of the FSM is based on a bridge design pattern [2]. A client class FSM contains a reference to an abstract server class ABSEventHandler and invokes its methods. The FSM class is completely independent of the server class implementation, thus is reusable in different contexts.

Listings 1 and 2 show these classes in their entirety. The FSM class keeps a private table (myptrArrayTrans) of transitions, where each element is basically a pointer to a TransitionType object. The structure TransitionType is nested in the FSM class and is defined as:

typedef struct
{
    int source_state;
    int destination_state;
    int event;
    int index;
} TransitionType;

This table is filled in by successive calls to a defineTransition member function when you encode the machine's behavior. Its size is determined by a private member myMaxNumTransitions. In addition, the FSM class contains another interesting private member, myCurrentState, which holds the current state. It also maintains a queue object needed to store received events.

The last private member is myptrHandler, a pointer to an object of a type derived from ABSEventHandler, as mentioned above. Given this pointer, an FSM object invokes an event handler, stored as a function pointer in an ABSEventHandler array functions.

We supply a very small public interface — only three functions besides a constructor are needed to manipulate the automaton. The constructor expects three arguments:

  • inTrans initializes a myptrHandler pointer.
  • inMaxNumTransitions determines the maximum number of transitions. It should be about ten to fifteen per cent greater than the expected maximum, to improve hash lookup.
  • inInitialState puts the machine in a well defined initial state through a myCurrentState member.

The cornerstone of this class is the defineTransition method, which lets you to give shape to your FSM. You define a particular transition, passing it a source state, a destination state (which may be the same as the source), the event id that triggers the transition, and a corresponding event handler index:

int FSM::defineTransition(
    int inSourceState,
    int inDestinationState,
    int inEvent,
    int inIndex )
{
    int index;
// Search free position in the table of transitions
    index = hash(inSourceState, inEvent);
    if ( index != -1 )
    {
        myptrArrayTrans[index].source_state =
            inSourceState;
        myptrArrayTrans[index].destination_state =
            inDestinationState;
        myptrArrayTrans[index].event = inEvent;
    myptrArrayTrans[index].index = inIndex;
    }
    return index;
}

Notice that a free position is located through an efficient hash method. If it is found, input parameters are mapped into a TransitionType structure members, otherwise the error value -1 is returned, indicating that the table is full.

During program execution you just call the method control, which looks up the user-defined event handler according to the event id and the current state. Next, the found event handler executes actions associated with a transition. No more nested switch statements, no more chains of case labels. Moreover, you can pass an arbitrary parameter that you can process in the event handler function. In our optimized implementation there is only one table lookup based on hash techniques. We also found a generateEvent method to be useful under some circumstances. For instance, we used it to generate an internal error event for missing transitions.

The constructor for ABSEventHandler takes a parameter indicating the size of an array of pointers to event handlers. Each element of this array stores a pointer to a function which handles a particular event. You need to define the method FillHandlersArray in a derived class to fill in this array. It is a pure virtual function. The abstract base class ABSEventHandler does not know how to do it, of course, so it delegates this task to a derived application-specific class.

Figure 1 shows the object model of the FSM framework presented here, together with a derived class Filter_EventHandler used in an example. We have used the Object Modeling Technique (OMT) developed by James Rumbaugh and others[3] to set up our analysis and design.

Using the Framework

Now let's look at the steps you must follow to use the framework:

1) Define all states and events needed for your application as an enumeration. Add indexes that will correspond to event handlers as shown in Listing 3. We defined this stuff together with a derived class Filter_EventHandler in separate files.

2) Derive a class from the abstract server class ABSEventHandler and define the pure virtual function FillHandlersArray of class ABSEventHandler. The purpose of FillHandlersArray is to fill in the array of pointers to event-handler functions. Define event-handler functions as member functions of a derived class for all events that need to be handled. You might need to define one event handler for each transition, or only a few handlers might do.

3) You still need to initialize the FSM with all possible transitions. As mentioned previously, this task is accomplished by succesive calls to defineTransition, passing it the source state, the destination state, the expected event, and the index in the array of event handlers. You will normally place this initialization somewhere in the beginning of a program.

4) Invoke the method control of the client class FSM upon reception of each event, passing it an event identifier and its associated parameters.

Listing 4 shows a C/C++ comment-filtering program that demonstrates how the framework is used. It takes two file names as command-line arguments, reads a C or C++ source file from the standard input stream, discards comments, and writes the rest of the input file to the output file. We left out error handling to keep the example as simple as possible.

Following the cook-book code, we define first events, states, and indexes. We could have used a series of macros, but standard C enumerations are far superior. For example, we defined the following enumeration for states:

enum Filter_states
{
    VALID = 0,
    WAIT_COMMENT,
    GARBAGE1,
    GARBAGE2,
    WAIT_END_COMMENT
};

Five states will do. An initial state is VALID, where each character read from the input file is written to the output file. Reception of a slash character puts the machine into the WAIT_COMMENT state. From here it is possible to make a transition back to the VALID state, or go on to the GARBAGE1 and GARBAGE2 states. GARBAGE1 corresponds to the inside of a C++ comment, and GARBAGE2 to the inside of a C comment.

Inside comments, characters are thrown away until the end of comment is found, which can be a newline character for a C++ comment, or an asterisk followed by a slash character for a C style comment, respectively. In the case of a C comment, the intermediate state WAIT_END_COMMENT is necessary to get back to the VALID state.

Figure 2 shows a complete dynamic model of the C/C++ comment filtering application.

In the second step, we derived a Filter_EventHandler class from abstract ABSEventHandler class and encapsulated file handling inside this class for the sake of simplicity. You would normally put system resources into a separate class. The derived class would contain just a reference to it, as shown in Figure 1. The main function first instantiates this class, passing it a maximal number of transitions and file names as parameters. The Filter_EventHandler constructor fills in the array of event-handler functionss and opens the files:

Filter_EventHandler::Filter_EventHandler(
    int inNumberTransitions,
    char* inputFile,
    char* outputFile )
    : ABSEventHandler(inNumberTransitions)
{
// Call this function from the constructor
    FillHandlersArray();
// Open input and output file
    ...
}

A Filter_EventHandler pointer is passed to the FSM constructor, along with the maximum number of transitions and an initial state. The next step is to define the FSM's behaviour. This is the crucial part of the program, because we model our filtering machine primarily by means of a set of defineTransition calls. If you detect some missing transition, you just add a new defineTransition call for that event.

There is the potential danger of an infinite loop, if the machine receives an event that has no defined handler. The FSM then generates an internal error event. The application should define an internal error handler to gracefully handle this situation. So if you cannot figure out all transitions, add one internal error transition for each state. We added only one, by way of illustration — a transition for the state GARBAGE1.

Now that we have defined a filter machine, the rest is easy. A while loop reads each character from the source file, converts it to an event, and calls control, passing it the event and read character. The event handlers writeChar and writeSlash use a cast from void* to int* to get a character and write it to the output file. If some error occurs, control returns false and the loop is terminated. The Filter_EventHandler destructor takes care of cleaning up and releasing resources.

The complete listing, together with an example is available electronically. See p. 3 for details.

Conclusion

We ended up with a simple plug-in FSM component that is portable across many platforms and is optimized for execution speed. Getting rid of ugly looking nested switch statements greatly improves the design of state-machine based programs. The basic component relies on a third-party list class to implement the event queue. You can use any commercial list implementation, or the STL queue template.

We initially developed this implementation on several flavors of UNIX (HP-UX V9.05, V10.10, Sun Solaris V2.3, Linux V1.2.13), then ported it to Windows NT V4.0 (Borland C++ V5.0 and Microsoft Visual C++ V4.1).

References

[1] Aamod Sane. Object-Oriented State Machines: Subclassing, Composition, Delegation, and Genericity. University of Illinois at Urbana-Champaign Department of Computer Science. 1994.

[2] Robert C. Martin. Designing Object-Oriented C++ Applications Using the Booch Method (Prentice Hall, Englewood Cliffs, NJ, 1995).

[3] James Rumbaugh. "OMT: The object model." Journal of Object Oriented Programming, January 1995.

Frantisek Kaduch was a software engineer with Marben, a company based in Madrid, Spain, specializing in OSI software. He has a degree in Electronic Engineering from Slovak Technical University, Slovakia. He has been developing telecommunications software for the past four years using C++ and object-oriented design. He is now working for Qualcomm, San Diego, and may be reached at [email protected].

Damian Jan is an OSI and TMN consultant. A former Marben employee, he now runs his own company, DEJ Consulting, based in Madrid, Spain and Vancouver, Canada. He has a degree in physics from UNCPBA University, Argentina, and has been involved in many telecommunications projects. He can be reached at [email protected].

Purificacion Vidal works for Kilowatt S.A., a Madrid, Spain based company, specializing in cellular telephony and CTI. She holds a Technical Engineer degree in Computer Science from the Polytechnic University of Madrid, Spain. She specializes in cellular telephony measurement systems software using OO architecture. She can be reached at [email protected].


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.