DEV Community

Cover image for State models
Tracy Gilmore
Tracy Gilmore

Posted on • Updated on

State models

Introduction

The terms State or Mode can be applied to software systems at a variety of scales from entire systems, sub-systems, down to an individual object/class or just a single property of an object. From here on we will refer to the "Subject" as the entity possessing the State. Whilst a Subject can possess many States for a variety of purposes, each one can only have a single value at a time.

Keeping track of the states a Subject can possess along with the how's and why's the Subject changes state can become complicated. Having a diagram to record all the states, the legal transitions between states (triggers) and why the transition might or might not occur (guard) can be extremely useful. Monitoring state changes on its own is seldom useful and is more often associated with firing off functions (actions), which can be attributed to both triggers and when entering/exiting a state.

UML - A brief introduction

The Unified Modeling Language has been the industry go-to for decades and is still important in sectors where detailed documentation is essential or even a contractual requirement. The UML specification is maintained by the OMG (not 'Oh My Gosh' but the) Object Management Group that also maintains the Data Distribution Service (DDS) specification and certification of Business Process Modeling and Notation (BPM).

UML comprises of around 14 diagrams but it is rare for all of them to be used in a project, at least not by a single contributor. Instead of using a drawing tool such as Visio or PowerPoint to capture UML diagrams there are specialist tools for the purpose that are categorised as Computer-Aided System/Software Engineering (CASE) tools. These special tools maintain a database (of one form or another) to store a UML model that ensures the consistent use of objects and relationships between diagrams.

There are two groups of diagrams that form the UML cannon:

  • Structural diagrams define how things are constructed/assembled.
  • Behavioural diagrams capture how components interact and co-operate.

UML Diagrams

However, there is a second (undocumented) aspect to the diagrams. Each diagram is intended to capture information from one interest group to inform another (or others) with each level becoming more technical. This covers the full spectrum of interest groups from users through to implementers and testers.


UML State Model - Part One - Keeping it simple

The simplest state model requires just three items:

  • States - Indicated by a rectangle containing the name of the state. The Subject can only be in one state at a time.
  • Triggers - link states with the legal paths, labelled with the event that causes the change, from one state to the other. The trigger has a single direction indicated by an arrow head.
  • Start indicator - points to the Subject's initial state.

Simple state model

There are state models that never end but most have one (possibly more) end state(s), identified by an End indicator.

The simple test case (timeless stopwatch)

We are going to base our first practical exercise on a simple stopwatch. It will not tell the time (so what good it will be is a fair question) but it will have four buttons (Start, Stop, Pause and Clear), the function of which should self-explanatory but illustrated by the following diagram. Side note: the Pause button will double as a Resume button.

State machine of a simple stopwatch

As stated earlier, a Subject (in this case the stopwatch) can only be in one state at a time (Clear, Stopped, Running or Paused) as illustrated above. The Clear state is also marked as being the initial state, there is no terminal state.

In case you were wondering, the diagrams in this post were prepared using the diagrams.net website.

"Time" for some JavaScript code

The source code for this practical can be found in this JSFiddle. It contains the HTML to display the current state and provide buttons to trigger state transitions. There is also a CSS file to style the state display panel. The JavaScript breaks down into a number of sections.

Wiring the DOM

The first two functions showState and enableButtons present the current state on the screen and connect the click event handlers to the button elements in the Document Object Model (DOM).

  • showState - presents the current state on screen
  • enableButtons - connects the click event of the buttons to the triggers of the State Model.
StateEngine

The third function is the major component and takes three parameters (machine, initial and subject).

  • machine defines the triggers and states of the state model.
  • initial indicates the state the engine will commence with.
  • subject is the data object that state applies to and happens to be a property of the object.
Commencement

The final section of the JS file contains a call to the enableButtons function, passing it the result of calling the StateEngine function with the following arguments.

  • State mode that reflects the diagram above:
  {
    'clear': {
      'START': 'running'
    },
    'running': {
      'STOP': 'stopped',
      'PAUSE': 'paused'
    },
    'paused': {
      'PAUSE': 'running'
    },
    'stopped': {
      'CLEAR': 'clear'
    }
  },
Enter fullscreen mode Exit fullscreen mode
  • The initial state of 'clear' and
  • the Subject object containing the state property.

Exercising this first example demonstrates a couple of things:

  1. Clicking the appropriate button will trigger the transition from the current state to the new state.
  2. More importantly, only the appropriate buttons trigger a transition, inappropriate buttons do nothing.

The question you are probably asking is 'what good is a state machine on its own'. The next section will address this question by maintaining the time for the stopwatch.


UML State Model - Part Two - Keeping time

In this practical we are extending the Subject to include a 'counter' property to record the time along with functions to reset the counter to '00:00.00' and increase the counter by 1/100 second.

We will be introducing another component called 'Clock' (implemented around a JS (technically the Web API) timer) that will response to functions (or Actions as they are known in UML) from the state machine and update the Stopwatch accordingly.

State machine of a stopwatch with timer

In the revised state diagram above the Clock is not show but there are a number of key changes that interact with it, including:

  1. A trigger has been added to the 'Running' state that is invoked when the Clock ticks. The new trigger has an Action to increase the Stopwatch counter every clock-tick but the actual state does not change (i.e. it loops back to the Running state.)
  2. Actions have also been attached to triggers in the model to support interaction with the Clock to: Stop, Start, Pause and Resume the timer.
  3. There is also an action attached to the Clear state itself. Actions attached to states are usually invoked either when the Subject enters or exits the state. In our example the action will be fired when entering the state but there is not support for exit actions in our implementation.

"Time" for some more JavaScript code

The source code that implements the above diagram can be found here.

Whilst the HTML remains unchanged the CSS is slightly revised to present the time is a style more akin to a physical stopwatch. But, as you might expect, the biggest change is to the JavaScript but not as much as you might think.

Before we dig into how the functions have changed we will take a look at the data.

State Model (enhanced)

As described above we have added _actions to the State Model, which has required a change to the structure.

{
  clear: {
    _action: () => {
      Subject.counter = 0;
      showState(Subject.counter);
    },
    START: {
      state: 'running',
      _action: () => Clock.start(),
    },
  },
  running: {
    STOP: {
      state: 'stopped',
      _action: () => Clock.stop(),
    },
    PAUSE: {
      state: 'paused',
      _action: () => Clock.pause(),
    },
    'CLOCK-TICK': {
      state: 'running',
      _action: () => {
        Subject.counter++;
        showState(Subject.counter);
      },
    },
  },
  paused: {
    PAUSE: {
      state: 'running',
      _action: () => Clock.resume(),
    },
  },
  stopped: {
    CLEAR: {
      state: 'clear',
    },
  },
}
Enter fullscreen mode Exit fullscreen mode

Two other changes to note:

  1. The are several references to the Clock object that we will explore in a little more detail later.
  2. The Subject object now includes a counter property as well as the state property.
const Subject = {
  state: '',
  counter: 0,
};
Enter fullscreen mode Exit fullscreen mode
The Clock

The Clock object is a wrapper around the Web API setInterval and clearInterval methods and knows nothing of State or any aspect of the Subject. It exposes four methods that facilitate interaction with the interval functions and calls the 'CLOCK-TICK' trigger of the State Machine when the timer is running.

  • start: commences the setInterval method to fire timer every 1/100th of a second
  • stop: cancels the setInterval method
  • resume: Same as start
  • pause: Same as stop

The showState function is revised to convert the counter parameter into a string before it is presented on screen.

The only change to the enableButtons function is that is returns the stateEngine it is passed in as a parameter.

Finally, the StateEngine function is extended to manage the _action properties, which are fired in two circumstances. An _action can be performed when the trigger is executed. It can also be fired when the state changes (on entry to the state) but as indicated above, the UML notation supports state actions on entry and on exit.


To Close

If this post has sparked your interest in state models in JS you might want to check out XState and this article by Jon Bellah.

Please add any questions you might have about state machines, UML or my examples in the discussion section below.

Further reading

The Rise Of The State Machines by Krasimir Tsonev
Finite State Machine in JavaScript by Linas Spukas

Top comments (0)