Click here to Skip to main content
Click here to Skip to main content

Object-oriented implementation of state-based logic

, 19 Jul 2004
Rate this:
Please Sign up or sign in to vote.
This paper addresses the problem of merging object-oriented and automaton-based programming technologies

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.

struct IFileAccess {
  virtual void Open( string const& _mode ) = 0;
  virtual void Close() = 0;
  virtual bool CanRead() const = 0;
  virtual bool CanWrite() const = 0;
};

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.

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();
  }
};

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.

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.

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.

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:

  • the interface and StateBased class must be inherited virtually;
  • the initial state-method main should be overridden to avoid ambiguity.

For example, suppose we have AppendFileAccess class with following transition graph.


Figure 4. Transition graph for 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.


Figure 5. Transition graph for 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;
  }
};

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).

References

  1. Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides Design Patterns - Elements of Reusable Object-Oriented Software. Addison-Wesley, 1995.
  2. Kevlin Henney State Government, C/C++ Users Journal C++ Experts Forum, June 2002
  3. Ted Faison Object-Oriented State Machines, Software Development Magazine, Sept, 1993.
  4. Paul Adamczyk The Anthology of the Finite State Machine Design Patterns, The 10th Conference on Pattern Languages of Programs 2003
  5. Harel D. Statecharts: A visual formalism for complex systems. Sci. Comput. Program., vol. 8, June 1987. pp. 231-274.
  6. Andre C. Representation and Analysis of Reactive Behaviors: A Synchronous Approach. CESA'96, Lille, France, IEEE-SMC, July 1996.
  7. Shalyto A.A. Switch-technology. Algorithmization and programming of tasks of logical control. SPb.: Nauka (Science), 1998.

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.

A list of licenses authors might use can be found here

Share

About the Author

Danil Shopyrin
Web Developer
Russian Federation Russian Federation
An experienced software developer.
 
Now I'm participating in VisualSVN project that is an integration package between Subversion and Visual Studio.

Comments and Discussions

 
GeneralI'm not sure that this is a good way to do state-based stuff PinmemberDon Clugston22-Jul-04 16:04 
GeneralWhat automaton uses is... PinmemberKochise22-Jul-04 19:58 
GeneralRe: What automaton uses is... PinmemberDon Clugston22-Jul-04 21:01 
GeneralRe: What automaton uses is... PinsussAnonymous28-Jul-04 5:13 
GeneralRe: What automaton uses is... PinmemberPlugwareSolutionsLtd30-Jul-04 12:23 
GeneralRe: What automaton uses is... PinmemberPlugwareSolutionsLtd30-Jul-04 12:17 
GeneralTOP is slow ! PinmemberKochise1-Aug-04 21:47 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.141022.2 | Last Updated 20 Jul 2004
Article Copyright 2004 by Danil Shopyrin
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid