Angela Brooks
UML has a rich set of notations for describing finite state machines (FSMs). In this chapter, we’ll look at the most useful bits of that notation. FSMs are an enormously useful tool for writing all kinds of software. I use them for GUIs, communication protocols, and any other type of event-based system. Unfortunately, I find that too many developers are unfamiliar with the concepts of FSMs and are therefore missing many opportunities to simplify. I’ll do my small part to correct that in this chapter.
Figure 15-1 shows a simple state transition diagram (STD) that describes an FSM that controls the way a user logs in to a system. The rounded rectangles represent states. The name of each state is in its upper compartment. In the lower compartment are special actions that tell us what to do when the state is entered or exited. For example, as we enter the Prompting for Login
state, we invoke the showLoginScreen
action. When we exit that state, we invoke the hideLoginScreen
action.
The arrows between the states are called transitions. Each is labeled with the name of the event that triggers the transition. Some are also labeled with an action to be performed when the transition is triggered. For example, if we are in the Prompting for Login
state and get a login
event, we transition to the Validating User
state and invoke the validateUser
action.
The black circle in the upper left of the diagram is called an initial pseudostate. An FSM begins its life following the transition out of this pseudostate. Thus, our state machine starts out transitioning into the Prompting for Login
state.
I drew a superstate around the Sending Password Failed
and Sending Password Succeeded
states because both states react to the OK
event by transitioning to the Prompting for Login
state. I didn’t want to draw two identical arrows, so I used the convenience of a superstate.
This FSM makes it clear how the login process works and breaks the process down into nice little functions. If we implement all the action functions such as showLoginScreen
, validateUser
, and sendPassword
, and wire them up with the logic shown in the diagram, we can be sure that the login process will work.
The lower compartment of a state contains event/action pairs. The entry
and exit
events are standard, but as you can see from Figure 15-2, you can supply your own events, if you like. If one of these special events occurs while the FSM is in that state, then the corresponding action is invoked.
Before UML, I used to represent a special event as a transition arrow that looped around back to the same state, as in Figure 15-3. However, this has a slightly different meaning in UML. Any transition that exits a state will invoke the exit
action, if any. Likewise, any transition that enters a state will invoke the entry
action, if any. Thus, in UML, a reflexive transition, such as that in Figure 15-3, invokes not only myAction
but also the exit
and entry
actions.
As you saw in the login FSM in Figure 15-1, superstates are convenient when you have many states that respond to some of the same events in the same way. You can draw a superstate around those similar states and simply draw the transition arrows leaving the superstate instead of leaving the individual states. Thus, the two diagrams in Figure 15-4 are equivalent.
Superstate transitions can be overridden by drawing explicit transition from the substates. Thus, in Figure 15-5, the pause
transition for S3
overrides the default pause
transition for the Cancelable
superstate. In this sense, a superstate is rather like a base class. Substates can override their superstate transitions the same way that derived classes can override their base class methods. However, it is inadvisable to push this metaphor too far. The relationship between superstates and substates is not really equivalent to inheritance.
Superstates can have entry
, exit
, and special events the same way that normal states can have them. Figure 15-6 shows an FSM in which both superstates and substates have exit
and entry
actions. As it transitions from Some State
into Sub
, the FSM first invokes the enterSuper
action, followed by the enterSub
action. Likewise, if it transitions out of Sub2
back to Some State
, the FSM first invokes exitSub2
and then exitSuper
. However, since it does not exit the superstate, the e2
transition from Sub
to Sub2
simply invokes exitSub
and enterSub2
.
Figure 15-7 shows two pseudostates that are commonly used in UML. FSMs come into existence in the process of transitioning out of the initial pseudostate. The transition leading out of the initial pseudostate cannot have an event, since the event is the creation of the state machine. The transition can, however, have an action. This action will be the first action invoked after the creation of the FSM.
Similarly, an FSM dies in the process of transitioning into the final pseudostate. The final pseudostate is never actually reached. Any action on the transition into the final pseudostate will be the last action invoked by the FSM.
I find diagrams like this to be immensely useful for figuring out state machines for subsystems whose behavior is well known. On the other hand, most systems that are amenable to FSMs do not have behaviors that are well known in advance. Rather, the behaviors of most systems grow and evolve over time. Diagrams aren’t a conducive medium for systems that must change frequently. Issues of layout and space intrude on the content of the diagrams. This intrusion can sometimes prevent designers from making needed changes to a design. The specter of reformatting the diagram prevents them from adding a needed class or state and causes them to use a substandard solution that doesn’t impact the diagram layout.
Text, on the other hand, is a very flexible medium for dealing with change. Layout issues are at a minimum, and there is always room to add lines of text. Therefore, for systems that evolve, I create state transition table s (STTs) in text files rather than STDs. Consider the STD of the subway turnstile in Figure 15-8. This can be easily represented as an STT, as shown in Table 15-1.
The STT is a simple table with four columns. Each row of the table represents a transition. Look at each transition arrow on the diagram. You’ll see that the table rows contain the two endpoints of each arrow, as well as the event and action of the arrow. You read the STT by using the following sentence template: “If we are in the Locked
state and get a coin
event, we go to the Unlocked
state and invoke the Unlock
function.”
This table can be converted into a text file very simply:
These 16 words contain all the logic of the FSM.
SMC (state machine compiler) is a simple compiler I wrote in 1989 to read STTs and generate C++ code to implement the logic. Since then, SMC has grown and changed to emit code for various languages. We’ll be taking a much closer look at SMC in Chapter 36 when we discuss the STATE pattern. SMC is freely available from the resources section of www.objectmentor.com.
Creating and maintaining FSMs in this form is much easier than trying to maintain diagrams, and generating the code saves lots of time. So, though diagrams can be very useful to help you think through or present an FSM to others, the text form is much more convenient for development.
Finite state machines are a powerful concept for structuring software. UML provides a very powerful notation for visualizing FSMs. However, it is often easier to develop and maintain an FSM by using a textual language rather than diagrams.
The UML state diagram notation is much richer than I have described. There are several other pseudostates, icons, and widgets that you can apply. However, I rarely find them useful. The notation I have described in this chapter is all I ever use.
34.229.151.93