Abstract

This paper addresses the problem of merging object-oriented and automaton-based
programming technologies. There are two major questions: "how to integrate an
automaton into an object-oriented system" and "how to implement an automaton
in the object-oriented fashion".

The problem of merging object-oriented and automaton-based programming technologies
is often analyzed in the literature [1-4]. This article gives only short description of
of the problem.

Introduction

In a traditional object-oriented system objects are communicating
with each other via their public interfaces. Each interface is a contract
between object and its clients. An interface exposes one or more methods.
A method can be treated as an event (message) with parameters and a return value.

It often happens, that a system contains one or more state-based
objects. The behaviour of these objects depends on their state,
where state can be represented as some scalar value. One of the ways to
represent this behaviour is a finite state machine.

A state-based object can be implemented with a following three-ply structure:

traditional object-oriented interface;

intermediate layer that converts methods to events;

underlying automaton-based logic.

The proposed approach corresponds to the main object-oriented paradigm
principles.

Encapsulation. The fact of state-based behaviour is hidden from the environment.

Polymorphism. If there is a number of state-based objects with a
different behaviour but with a common interface, then a user can interact
with them in a uniform manner.

Inheritance. The behaviour of a state-based object can be extended
using ordinary inheritance mechanism.

Object-oriented interface

There are no any awkward restrictions to state-based objects interfaces.
These interfaces can contain a number of methods. A method can accept a number
of parameters and can have a return value. Some methods can be marked as constant.

For example, the interface intended to control file access rights is shown below.

Intermediate layer

This layer is a routine. It is just an interface implementation where each
method call is converted to an event object, the latter is propagated to an
underlying logic layer and then, after processing, the return value and output
parameters are returned to the caller.

This class should be derived from an interface and from the common
implementation facility as shown on the picture below.

Figure 1. Intermediate layer inheritance model

Firstly, a particular event type should be defined for each method. Each
of these types should be derived from StateBased::Event type.
There are no more limitations to the structure of these types. These types
can be declared inside of FileAccessBase class.

Secondly, all methods from the interface class should be implemented.
These implementations have similar structure: the corresponding event
object is created on the stack and passed to
StateBased::Execute() method.

Automaton-based logic

There is a human readable way to represent automaton's logic -
transition graph. This method is used in Statecharts [5], SyncCharts [6],
SWITCH-technology [7] etc. Each automaton's state in a transition graph is
drawn as rounded rectangle. Each transition between states is drawn as
arrow. Arrows are labeled in <condition>/[<action>] format.
Brackets mean that action part is unnecessary.

One of possible transition graphs for mentioned example is shown below.
This logic allows us to have two file opening modes - reading
and writing.

Figure 2. Transition graph for FileAccess class

There are two major ways to implement automaton:

using dynamic function table.

using operator switch with nested if
conditions (this method is used in SWITCH-technology).

This paper provides a new one. The approach is based on a built-in
virtual methods mechanism. The space of automaton's states is mapped on the
virtual methods set. These methods are called state-methods. All
state-methods have the same signature. Each automaton's state corresponds
to the only one state-method and vice versa. The instance of state-based
object holds current state as a reference to the current state-method.
There is a special state-method called main, which is treated
as an initial state.

With these presumptions the automaton from Fig. 2 can be easily
implemented as following.

Each state-method should return true if transition is done
and false otherwise. It is accepted that if there is no any
transition done for a method call, then
StateBase::UnexpectedOperation exception is thrown.

Logic extension via inheritance

The main advantage of the proposed approach is the possibility of
using inheritance. The behaviour of an object in a particular state can be changed or
extended. New states can be added to the automaton.

For example, we can extend our FileAccess object by adding
a mixed read/write mode. The corresponding automaton is shown below.

Figure 3. Transition graph for RWFileAccess class

The asterisk over state main means that this state is
extended, i.e. all the transitions from main to other states
are saved in the new automaton. The code for extended version of file
access control looks like following.

Further, we can join AppendFileAccess and
RWFileAccess into a single state based object that can work in
four modes - reading, writing,
readwriting and appending. The corresponding
automaton looks as following.

Back-end facility

There is a back-end class called StateBased. All the
state-based classes must be derived from it. The StateBased
class provides following:

base class for all event objects;

const and non-const versions of Execute() method which is
used by intermediate layers;

SetState() method which is used to change the object's state;

EventCast() template method which is used to determine
particular type of an event object.

Maintaining immutability

Unfortunately, the automatic control of object immutability is broken.
During the construction of a state-based object instance the non-const
reference to it is remembered. And even if you call a const interface
method - the non-const reference is used to call state-method.

However, StateBased class automatically controls the
immutability of object's state in runtime. If const interface method is
called and current state-method tries to call SetState()
method, then StateBased::ReadOnlyViolation exception will be
thrown.

But if there are some user data in the class, then immutability should
be maintained manually. If non-const access to the data is requested and
StateBased::IsImmutable() is true, then
ReadOnlyViolation should be thrown.

Conclusion

The proposed approach is well suitable under following conditions:

there are many objects with the same interface but with different behaviour;

the hierarchy of logic can be introduced for this objects;

there is a little of data in objects beside their states (no any data is an ideal case).

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

It's good to write article...but...but...
I don't really read your article, so I may made mistake...but...

Why do you use struct to define interface class. there a c++ word for this "interface",
Reference: "MSDN library - Platform SDK: Automation - interface.
I use it in Visual C++ 6\.net\.net 2003 to write Directshow filter an other COM component like ActiveX...

In C++ you use inherence but in UML model you mustn't use it, use: ----() IFileAccess
Reference: "The unified Modeling Language Reference manual" page 55.

Despite what you say in the article, your code isn't really polymorphic. The heavy use of EventCast<> is a giveaway. True polymorphic code would use virtual functions instead of switch / multiple if statements.

Being able to use inheritance to build a more complex automaton is a good idea, I think. But, there are other ways of achieving the same thing. You may be better off with a table-based method. If there was a virtual function which looked up the state in the table, you could use inheritance similar to the way you've described (the derived class would explicitly deal with the shared states. Any state which was unrecognized would be passed to the base classes, if any).

It just looks to me as though the idea of implementing states as functions becomes messy once either there are a significant number of events, or when the state actually does something in response to the event.
Also, your code for returning bool values gets clumsy very quickly.

...strict and non pylomorphic state based logic. When you code and compile your code, it won't morph either :/

I think the state based logic is another way to get an overview and structure the code. A bit like when people gone from straigh C to C++ with object oriented programming. Just another way of conception...

About the table-based method, which surely improve the maintenace (I done such a thing in a previous firm for a... device driver), you better have to use a karnaugh simplifier. But this doesn't really work at compile time (see meta programming).

And keeping the tables open, scan them or use an index to use tables as converter, it's a bit like pseudo-coding with virtual machine code. It's slow...

I think the state based logic described by Danil Shopyrin is quite interresting as it will be machine code compiled at full speed. Just have to indent correctly to show the state machine structure.

> ...strict and non pylomorphic state based logic. When you code and compile your code, it won't morph either :/

Agreed. I was just pointing out that 'polymorphic' was not an accurate description of the code. I don't think that is necessarily a bad thing. In fact, I don't think there is any need for the state functions to be declared as 'virtual'. In every case, they could be resolved at compile time.

State-based logic doesn't seem to translate very well at all to an object-oriented design.

I wonder if you could do better with generic programming.
I can envisage a table which exists only at compile time (the function is only ever accessed with a compile-time constant(the state)). Perhaps you could get all of the 'if' statements generated automatically by templates. Would make a good challenge for Andrei Alexandrescu, I think.

BTW, Danil, sorry if my earlier post seemed negative. I do think that this is an area where no-one has yet come up with an ideal solution. Your article has certainly piqued my interest.

People are working on this already - try looking at the boost::fsm metaprogramming implementations of finite state machines in the boost cvs sandbox, http://www.boost.org/more/mailing_lists.htm#sandbox

I agree with Don. In my experience attempts at decomposition of state based systems into object oriented designs feel 'brittle' and remind me more of a clever hack than a meaningful way to solve the problem. What's more, they are not very well suited to adaptation and maintenance ...

I feel that TOP is the key to the implementation of state based systems:

Like I said, TOP is like a VJM and should parse the table at run-time. I'm working on a bit table/tree that is fitted into a bitstream pipelined engine that automaticaly autoput the next expected state according to the entry. Mostly like a turning machine... Just a table of callback pointers should be provided and a bitstream table as configuration.