A crash course in UML state machines: Part 3
High-level design
Figure 2.14 shows first steps in elaborating the calculator statechart. In the very first step (panel (a)), the state machine attempts to realize the primary function of the system (the primary use case), which is to compute expressions: operand1 operator operand2 equals... The state machine starts in the "operand1" state, whose function is to ensure that the user can only enter a valid operand. This state obviously needs some internal submachine to accomplish this goal, but we ignore it for now.
The criterion for transitioning out of "operand1" is entering an operator (+, –, *, or /). The statechart then enters the "opEntered" state, in which the calculator waits for the second operand. When the user clicks a digit (0 .. 9) or a decimal point, the state machine transitions to the "operand2" state, which is similar to "operand1." Finally, the user clicks '=', at which point the calculator computes and displays the result. It then transitions back to the "operand1" state to get ready for another computation.
Figure 2.14: The first two steps in elaborating the calculator statechart
The simple state model from Figure 2.14a has a major problem, however. When the user clicks '=' in the last step, the state machine cannot transition directly to "operand1" because this would erase the result from the display (to get ready for the first operand). We need another state "result" in which the calculator pauses to display the result (Figure 2.14b). Three things can happen in the "result" state: (1) the user may click an operator button to use the result as the first operand of a new computation (see the recursive production in line 2 of the calculator grammar), (2) the user may click Cancel (C) to start a completely new computation, or (3) the user may enter a number or a decimal point to start entering the first operand.
TIP: Figure 2.14b illustrates a trick worth remembering: the consolidation of signals PLUS, MINUS, MULTIPLY, and DIVIDE into a higher-level signal OPER (operand). This transformation avoids repetition of the same group of triggers on two transitions (from "operand1" to "opEntered" and from "result" to "opEntered"). Although most events are generated externally to the statechart, in many situations it is still possible to perform simple transformations before dispatching them (e.g., a transformation of raw button presses into the calculator events). Such transformations often simplify designs more than the trickiest state and transition topologies.
Scavenging for reuse
The state machine from Figure 2.14b accepts the C (Cancel) command only in the result state. However, the user expects to be able to cancel and start over at any time. Similarly, the user expects to be able to turn the calculator off at any time. Statechart in Figure 2.15a adds these features in a naïve way. A better solution is to factor out the common transition into a higher-level state named "on" and let all substates reuse the C (Cancel) and OFF transitions through behavioral inheritance, as shown in Figure 2.15b.
Figure 2.15: Applying state nesting to factorize out the common Cancel transition (C).
Elaborating composite states
The states "operand1" and "operand2" need submachines to parse floating-point numbers. Figure 2.16 refers to both these states simultaneously as "operandX" state.
Figure 2.16: Internal submachine of states "operand1" and "operand2."
These submachines consist of three substates. The "zero" substate is entered when the user clicks 0. Its function is to ignore additional zeros that the user may try to enter (so that the calculator displays only one 0). Note my notation for explicitly ignoring an event. I use the internal transition (DIGIT_0 in this case) followed by an explicitly empty list of functions (a semicolon in C).
The function of the "int" substate is to parse integer part of a number. This state is entered either from outside or from the "zero" peer substate (when the user clicks 1 through 9). Finally, the substate "frac" parses the fractional part of the number. It is entered from either outside or both peer substates when the user clicks a decimal point ('.'). Again, note that the "frac" substate explicitly ignores the decimal point POINT event, so that the user cannot enter multiple decimal points in the fractional part of a number.


Loading comments... Write a comment