The State Design Pattern applied to OMNeT++ models

When we need to add our own behavior to a cSimpleModule, we basically have to create a new class by deriving it from cSimpleModule and then we override a couple of methods and add whatever other methods we need to accomplish our needs. These two methods are initialize() and handleMessage().

The initialization code is placed in the initialize() method. This is also the place where a few initial events can be scheduled. However, the method of interest is handleMessage(). This method is called by the simulation core whenever an event is pulled out from the Future Event Set (FES).

When the complexity of the logic inside handleMessage() method starts growing, the code becomes hard to read and update. Sometimes, the code to be executed depends on the actual state of the model. An elegant implementation to manage complexity is the State Design Pattern.OMNeT++ class hierarchy

In the left image, a partial class hierarchy from OMNeT++ simulation library is shown. This partial hierarchy starts with the root class, cObject, and extends itself until the building blocks of OMNeT++ models: Modules, both simple (cSimpleModule) and compound (cCompoundModule).

The initialize() and finish() protected virtual methods are defined in the cComponent class. They can be overriden in our cSimpleModule derived classes. Within initialize() we will place our initialization code. On the other hand, finish() will be called after successful simulations have finished.

A key component is the state class hierarchy that can be seen in the below figure. The class cBaseState is abstract. The class declares two pure virtual methods: handle() and reset(). The handle() method implemented in every descendant will contain the code. The reset() method will be used if there is need of reseting state before leaving the state.

Even though the model shows that cBaseState is derived from cObject, this is completely optional. It has been designed this way in order to improve the integration with the simulation environment. It could also be derived from a class derived from cObject, e.g. cNamedObject, cOwnedObject or cNoncopyableOwnedObject.

cBaseState will hold a pointer to the owner cStateMachine. This pointer will be useful when we need to reach the cStateMachine object and read its state variable, even for setting the next state. This pointer will be initialized by the cBaseState constructor, so when the cStateMachine object instantiates the machine states, it will pass to the constructors a pointer to itself (this).

An important design point is where the decision to which state will be the next is going to be taken. The natural choices are two: In the cStateMachine class or in every single descendant of the cBaseState class.

More information on State Design Pattern can be read in the wonderful book written by Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides "Design Patterns. Elements of Reusable Object-Oriented Software". Another nice book on design patterns, where the State Design Pattern is described in the 10th chapter, is "Head First. Design Patterns" written by Eric Freeman and Elisabeth Freeman.

Model class diagram