![]() |
Development Lifecycle »
Design and Architecture »
Design and Strategy
Beginner
Object-oriented implementation of state-based logicBy Danil ShopyrinThis paper addresses the problem of merging object-oriented and automaton-based programming technologies |
VC7.1, WindowsVS.NET2003, Dev
|
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
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.
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:
The proposed approach corresponds to the main object-oriented paradigm principles.
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.
struct IFileAccess { virtual void Open( string const& _mode ) = 0; virtual void Close() = 0; virtual bool CanRead() const = 0; virtual bool CanWrite() const = 0; };
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.

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.
class FileAccessBase : public virtual IFileAccess, protected virtual StateBased { protected: struct EOpen : public Event { EOpen( string const& _mode ) : mode( _mode ) {} string const& GetMode() const { return mode; } private: EOpen& operator=( EOpen const& ); string const mode; }; struct EClose : public Event {}; struct BoolEvent : public Event { BoolEvent() : result( false ) {} bool GetResult() { return result; } bool SetResult( bool _result ) { result = _result; return true; } private: bool result; }; struct ECanRead : public BoolEvent {}; struct ECanWrite : public BoolEvent {}; /*...*/ };
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.
class FileAccessBase : public virtual IFileAccess, protected virtual StateBased { /*...*/ public: virtual void Open( string const& _mode ) { EOpen event( _mode ); Execute( event ); } virtual void Close() { EClose event; Execute( event ); } virtual bool CanRead() const { ECanRead event; Execute( event ); return event.GetResult(); } virtual bool CanWrite() const { ECanWrite event; Execute( event ); return event.GetResult(); } };
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.

FileAccess class
There are two major ways to implement automaton:
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.
class FileAccess : public FileAccessBase { protected: virtual bool main( Event& _event ) { if ( EOpen* e = EventCast< EOpen >( _event ) ) { if ( e->GetMode() == "r" ) return SetState( *this, &FileAccess::reading ); else if ( e->GetMode() == "w" ) return SetState( *this, &FileAccess::writing ); } return false; } virtual bool reading( Event& _event ) { if ( ECanRead* e = EventCast< ECanRead >( _event ) ) return e->SetResult( true ); else if ( ECanWrite* e = EventCast< ECanWrite >( _event ) ) return e->SetResult( false ); else if ( EClose* e = EventCast< EClose >( _event ) ) return SetState( *this, &FileAccess::main ); return false; } virtual bool writing( Event& _event ) { if ( ECanRead* e = EventCast< ECanRead >( _event ) ) return e->SetResult( false ); else if ( ECanWrite* e = EventCast< ECanWrite >( _event ) ) return e->SetResult( true ); else if ( EClose* e = EventCast< EClose >( _event ) ) return SetState( *this, &FileAccess::main ); return false; } };
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.
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.

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.
class RWFileAccess : public FileAccess { protected: virtual bool main( Event& _event ) { if ( FileAccess::main( _event ) ) return true; if ( EOpen* e = EventCast< EOpen >( _event ) ) if ( e->GetMode() == "rw" ) return SetState( *this, &RWFileAccess::readwriting ); return false; } virtual bool readwriting( Event& _event ) { if ( ECanRead* e = EventCast< ECanRead >( _event ) ) return e->SetResult( true ); else if ( ECanWrite* e = EventCast< ECanWrite >( _event ) ) return e->SetResult( true ); else if ( EClose* e = EventCast< EClose >( _event ) ) return SetState( *this, &RWFileAccess::main ); return false; } };
Moreover, a multiple inheritance is also available. The logic of a several automatons can be joined. There are two major requirements for this option:
StateBased class must be inherited virtually;
main should be overridden to avoid ambiguity.
For example, suppose we have AppendFileAccess class with
following transition graph.

AppendFileAccess class
The code for this class is shown below.
class AppendFileAccess : public FileAccessBase { protected: virtual bool main( Event& _event ) { if ( EOpen* e = EventCast< EOpen >( _event ) ) if ( e->GetMode() == "a" ) return SetState( *this, &AppendFileAccess::appending ); return false; } virtual bool appending( Event& _event ) { if ( ECanRead* e = EventCast< ECanRead >( _event ) ) return e->SetResult( false ); else if ( ECanWrite* e = EventCast< ECanWrite >( _event ) ) return e->SetResult( true ); else if ( EClose* e = EventCast< EClose >( _event ) ) return SetState( *this, &AppendFileAccess::main ); return false; } };
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.

RWAFileAccess class
There is a very laconic code for this class.
class RWAFileAccess : public RWFileAccess, public AppendFileAccess { protected: virtual bool main( Event& _event ) { if ( RWFileAccess::main( _event ) ) return true; if ( AppendFileAccess::main( _event ) ) return true; return false; } };
There is a back-end class called StateBased. All the
state-based classes must be derived from it. The StateBased
class provides following:
Execute() method which is
used by intermediate layers;
EventCast() template method which is used to determine
particular type of an event object.
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.
| You must Sign In to use this message board. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 19 Jul 2004 Editor: Nishant Sivakumar |
Copyright 2004 by Danil Shopyrin Everything else Copyright © CodeProject, 1999-2009 Web18 | Advertise on the Code Project |