The comment stripper is a simple example, and the FSM it demonstrates doesn't deal with window management. As a slightly more
realistic example, let's look at an implementation of the GUI from Figure 5. The FSM controls the hypothetical Function 1, and the FSM starts when that function is chosen from the Function menu. Other functions would be
implemented with their own FSMs, which is straightforward because the FSM was built as a class. We're not really implementing the whole program here, just the window-management parts; all the event routines are there,
so adding the code to do the database actions (yawn) would be painless. The second thing you'll notice, right after you notice those bizarre event names, is that the nice, friendly action
routine names have gone, replaced by the anonymous subroutines a01 through a44. With 44 subroutines to code, the only sensible names are systematic ones – using the state and event names as before is just
too unwieldy. In fact, the action names are irrelevant because their corresponding state/event combinations are much more useful identifiers. Here's a portion of the FSM table definition:
FSM.TableEntry A__, A_Ok_____, EXI, AddressOf a01 FSM.TableEntry A__, A_Cancel_, EXI, AddressOf a02 FSM.TableEntry A__, A_Apply__, A__, AddressOf a03 FSM.TableEntry A__, A_Details, AB_, AddressOf a04
FSM.TableEntry A__, A_More___, AC_, AddressOf a05 FSM.TableEntry A__, B_Ok_____, ERO FSM.TableEntry A__, B_Cancel_, ERO The key description of this code is 'systematic', which is also why we've
adopted such a strange convention for the state and event names. We're fighting Visual Basic's unreasonable layout restrictions by making the names the same length so that the list of TableEntry calls is
readable. We can't quite make a table layout as in the C code example earlier, but the result is an acceptable facsimile that is reasonably self-documenting. Notice that two pseudo-states have been
introduced for this example: EXI, which represents termination of the FSM, and ERO, which denotes an error condition. Neither of these conditions should be encountered by the FSM: EXI successor states are never reached
because the action routines associated with their transitions halt the FSM, and ERO successor states can be derived only from illegal inputs. The FSM driver function (FSM.EvHandler) traps these pseudo-states and
raises an FSM_Error event. This is the FSM equivalent of a Debug.Assert statement. The use of ERO states also permits us to omit coding for state transitions that will never happen. As well as
modifying the driver to raise an error on illegal transitions, we've also modified the TableEntry method to make the action function optional. In this case, it saves 12 action functions and nicely distinguishes
error conditions in the matrix. It's tempting to omit these lines from the list, but we should avoid the temptation vigorously, because if we do so we can no longer tell whether we've covered all possible situations by
simply counting the table entries. Another temptation is to factor code by reusing action routines – for example, a01 and a02 appear to be the same, as do a12 and a13
. However, discarding a02 and wiring up a01 in its place can be disastrous because it introduces a dependency that will cause problems if we later want to change the actions for either transition
independently of the other. We could, of course, define a helper subroutine that's called by both action routines (ConfirmDiscardEdits is such a function). Remember that a system
is useful because it takes some of the intellectual load off managing complexity, and it goes without saying that circumventing the system – for whatever reason – stops it from being systematic. One final
comment about this example is that it doesn't include validation or confirmation states. Such states would amplify the complexity by adding a new state for each OK and Cancel event, along with 11 corresponding table
entries (in this case). In real life, validation and confirmation are best handled by building a conditional mechanism into the FSM. This does not
mean we should do such processing ad hoc, and control over the successor state should remain with the FSM driver function (FSM.EvHandler). This means you can't use Visual Basic's Form_QueryUnload or
Form_Unload event to trigger validation or confirmation since a form unload must always succeed (canceling an unload from QueryUnload will cause havoc because the FSM thinks the form has been unloaded and now its
state information is incorrect). An acceptable way to implement both types of condition is to add an abort transition method to the FSM class: Public Sub AbortTransition()
TransitionAborted = True End Sub Now we can modify the FSM driver to check the TransitionAborted flag before setting the successor state: Public Sub EvHandler
... CallMe m_aatFSMTable(m_nCurrentState, wparam).pcbAction If Not TransitionAborted Then
m_nCurrentState = m_aatFSMTable(m_nCurrentState, _
wparam).nNextState
End If ... End Sub This might be simple, but it adds considerable complexity to the action routines because we must be very careful about which forms we
unload. More specifically, if we cancel a transition we need to be sure that we don't change anything that characterises the current state. In this case, the states are defined entirely in terms of forms, so we need to
ensure that the action routine has the same forms loaded when we leave that were loaded when we entered. For example, assuming we're in state AB_ (forms A and B loaded), we need either to unload both forms or to leave
them both loaded. The following code correctly describes the validation logic for an A_Ok event in this state: Public Sub a12() Dim bUnload As Boolean
bUnload = True If frmDetails.Dirty Or frmSummary.Dirty Then If Not bConfirmDiscardEdits Then
bUnload = False End If End If
If bUnload Then
Unload frmDetails Unload frmSummary Else FSM.CancelTransition
End If End Sub Next... |