Notice: This Wiki is now read only and edits are no longer possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.
OTPattern/StateRole
Contents
Pattern "State Role"
Intent
Use a set of role classes for implementing a state machine.
Motivation
When implementing a protocol statemachine it may be desired to keep the statemachine well separated from the basic behavior of the given component, i.e., the statemachine acts mostly as a monitor, but may also intercept basic behavior at specific points.
Also the implementation of the statemachine should be modular, i.e, one may want to realize each state as a separate module in order to achieve optimal correspondence between model and implementation. Furthermore, states may be refined into sub-statemachines, which should be directly reflected by nesting in the implementation.
Structure
In OT/J a statemachine can easily be realized by a team containing one role for each state. All roles are bound to the base class under consideration.
Collaboration
Each State role intercepts specific triggers of the base object. Depending on the defined transitions, the role may
- block the behavior
- add its own behavior
- switch to a next state
In this pattern state switching is realized as follows:
- Each role is guarded as to be active only upon explicit registration (see ObjectRegistration).
- A switch is performed by unregistering the current role and creating the next State role.
Consider this code fragment:
public team class Watcher { protected class Dirty playedBy Editor base when (Watcher.this.hasRole(base, Dirty.class)) // see pattern ObjectRegistration { warn <- replace close; @SuppressWarnings("basecall") // no base call: block base behavior callin void warn() { System.out.println("!!!refusing to close dirty editor!!!"); } save <- after save; void save() { unregisterRole(this, Dirty.class); new Clean(this); // pass the base object using implicit lowering. } } protected class Clean playedBy Editor ... }
Given that an instance of role Dirty
exists, then invocations of Editor.close()
will be blocked and after executing Editor.save()
the statemachine will switch to state Clean
. The idea behind this is, that the Watcher team will contain exactly one role instance at any time. Only this role instance will receive triggers from the base object.
A tricky point may be how to create the first role representing the initial state. Here are some options:
- A role for the initial state has a base guard with inverted logic: receive callin triggers only until a role has been instantiated:
base when (!Watcher.this.hasRole(...))
- This realizes a role that receives at most one trigger.
- An initial role can be created when creating the team using declared lifting as in
Watcher(Editor as Initial state) { ... }
Variations
If nested statemachines are required, a role just needs to be marked as team, too, so that it can contain the nested statemachine. Such compound states should call activate()
in their constructor and deactivate()
when leaving this state, as to enable/disable the callins of nested states.
Such a role-and-team object can directly implement a history connector: when deactivating the state without discarding the currently active role, re-activating the state will re-establish the previous (inner) state.
Complex Example
(to follow)
Known Uses
- A similar structure has been developed for the SalesPoint framework (details to follow).
- Sokenou+ developed a similar technique for implementing state charts as test oracles.
Related Patterns
- InnerState shows how states of an object can be encapsulated in the object itself.