Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Object-Oriented Implementation of State-Based Logic

0.00/5 (No votes)
19 Jul 2004 1  
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:

  1. "How to integrate an automaton into an object-oriented system?"  
  2. "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 a short description 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 are 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 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 the 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 labelled in <condition>/[<action>] format. Brackets mean that action part is unnecessary.

One of possible transition graphs for the 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 follows:

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 the 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 the 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 follows:

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 the 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 is 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 the 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 data in objects beside their states (no 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.

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