Re: State Machine/Class Granularity
- From: "H. S. Lahman" <h.lahman@xxxxxxxxxxx>
- Date: Fri, 15 Jul 2005 18:29:10 GMT
Responding to Wavemaker...
As a sanity check, I'm going to describe some of my progress writing a state machine code generator; it's in the form of a builder class. Any comments or criticisms are welcome.
After tweaking it over the past couple of days, I've settled on what I hope is a useful design. The StateMachineBuilder class builds state machines as strings representing the actual state machine code. After being built, the string can be written to file, printed or whatever. He's a snippet of code of the StateMachineBuilder in action:
Let me ask a very basic question: What is the purpose of your builder? Is it simply to generate the nuts & bolts FSM infrastructure code in an application with FSMs for a known problem? Or is the intent to build FSMs dynamically (e.g., the FSM itself is defined at run-time)? In the first case the builder is just a tool for providing automation of code writing as a convenience to the developer. The second case is more like what a commercial transformation engine does (i.e., taking arbitrary specifications from an external source and constructing code in a problem-independent manner).
The reason I ask is that you mention getting input from a UI, which suggests the FSMs are being designed "on the fly", either at run-time or as part of a higher level (RAD-like) IDE for designing applications.
If you are just looking for an automation tool for individual applications you might think about simply using source templates. Most of the infrastructure is defined in lookup tables which have to be filled in anyway and the actions are normal methods whose bodies have to be written anyway. In addition, languages like C++ have convenient facilities (macros, #define, etc.) for managing synchronization. Synchronization is more manual but one can help oneself through process (e.g., comments in the template: if THIS table is updated, check consistency with THAT table).
What you describe below is pretty much what a translation transformation engine does when it processes a Statechart from a UML drawing tool. Instead of a UI, an XMI reader for the drawing tool repository is providing all the ASCII strings you use as arguments. That's fine and I applaud your initiative but it may be overkill unless you have such a generic construction requirement that externalizes the FSM specifications. OTOH, if you already have most of the code done... B-)
StateMachineBuilder builder = new StateMachineBuilder();
builder.AddUsing("System");
builder.NamespaceName = "StateMachineExample"; builder.ClassName = "Keyboard";
// State Event NextState Action builder.AddTransition( "Default", "CapsLock", "CapsLocked", null);
builder.AddTransition( "Default", "AnyKey", "Default", "DisplayLowercase");
builder.AddTransition( "CapsLocked", "CapsLock", "Default", null);
builder.AddTransition( "CapsLocked", "AnyKey", "CapsLocked", "DisplayUppercase");
builder.InitialState = "Default";
builder.Build();
Console.WriteLine(builder.StateMachine);
null means a no operation (a NoOp method is substituted by the state
machine). For invalid operations, an "Invalid" action would be
appropriate. The builder would then create an Invalid action method in
which the logic for dealing with invalid transitions could be placed.
Also, self-transitions are handled by having the next state name the same as the current state name. Maybe there would be a better way to indicate this such as using hyphens (e.g. "--") in the next state category?
You really don't care if an event is reflexive to the state because the table takes care of getting the right action in either case. Conceivably it could be a performance enhancement so that one doesn't have to do ASCII key lookups somewhere. However, reflexive events are rare enough that simply checking for the special case for every event probably makes any gain moot.
However, this segues to another issue of performance. You don't want to be doing STT lookups using ASCII keys. So State and Event want to be converted to a nice integer index somewhere along the line. In a translation transformation engine that would one the primary jobs of the builder. It would also make sure the conversion table was around so that when code was created from the text-based AAL text event generation the integer indices would be substituted in the event ID in the produced code.
IOW, apropos of the point above, a major reason one has a builder in a general purpose code generator is because one /must/ convert convenient ASCII 4GL representation into something more efficient. But for your own development of an individual program you already know exactly what needs to be done so you can bypass the conversion step. (More precisely, encode it directly in tables.)
What I like about this is that the STT is built right into the code. It's clearly laid out (erm, with the exception of some line wrapping I had to put in for this post). Also, it would be easy to build a GUI front end to make state machine generation even easier.
Right. The calls to addTransition are essentially just laying out an STT. You will even group the calls together and comment them as if it were an STT. It's the same table with some syntactic sugar.
As far as the GUI is concerned, you are providing an interface that doesn't care where the ASCII comes from. IOW, the interface captures the invariants of FSM creation and parameterizes the details in nice, portable, source-indpendent ASCII via the STT paradigm. Note that your builder interface could be a Facade class that serves as a subsystem interface to the FSM construction subject matter. Then one can freely substitute the source between GUI, browser, command line, XMI reader, etc. That's another justification for doing things as you have and you may have gotten to exactly the same place purely from the perspective of application partitioning.
Here is part of the code generated by the builder; it's in the constructor of the state machine:
eventQueue.Subscribe("CapsLock", 0, this); eventQueue.Subscribe("AnyKey", 1, this);
The event queue is passed to the state machine's constructor. The state machine knows which events it's interested in, so it subscribes to those events there. The first parameter of the Subscribe method is the name of the event. The event name does not have to preexist before being subscribed to. If it doesn't already exist, the event queue "registers" it and gives it a unique event ID. The second parameter is the substitute event ID the event queue should use for this state machine when the specified named event is raised. And the last parameter is, of course, the state machine itself so that the event queue has an instance to dispatch the subscribed events to.
Very good. However, I am concerned about the mechanism the event queue uses to provide the mapping. It will have a target instance address in hand from the event itself. Is the intention to match that with subscribed 'this' pointers to determine the substitute event ID?
What I am getting at is that I think you need some other mechanism for identifying the class. You are going to be accessing a static member anyway, so all you need is the class name to generate code (e.g., <class name>::consumeEvent(...) in C++) and you will have a target instance handle in hand with the event to pass as an argument. If you employ a unique integer class identifier for each class, you can register both the same way
eventQueue.ClassSubscribe("Keyboard", 14);
....
eventQueue.Subscribe("CapsLock", 0, "Keyboard");
....(Though in a full code generator one would probably keep the class mapping table somewhere else that the FSM builder accesses because it would be useful in other contexts.)
Now the event has '14' as a target class ID and some number for "Capslock" to use to look up the '0' for the local event ID. At the same time it uses '14' to look up the class name to insert in the code for the call.
In addition to some other boiler plate code, the builder generates empty action methods, for example:
private void DisplayLowercase(object data) { // TODO: DisplayLowercase logic. }
private void DisplayUppercase(object data) { // TODO: DisplayUppercase logic. }
What's interesting to me is how this approach to state machines has driven my design. For example, I had designed a state machine previously representing a slave clock. This clock receives MIDI clock messages from an external device, interprets them, and generates tick events at the specified timing resolution. The clock periodically checks (about every half-second or so) to see if it is still receiving clock messages. If it has stopped receiving clock messages (say the MIDI cord to the computer became unplugged), it would need to raise an event to let the rest of the subsystem know and transition back to a default state.
I had put this logic in the form of a guard inside a "Running" state handler. But this required the handler to know how to make transitions. This is a no-no, I've since learned. :-) With this approach to state machines, the action handlers do not have any knowledge about states or transitions, so I was faced with a challenge when redesigning the state machine. I solved the problem with the addition of another state. This got rid of any need for a guard, and since I dislike them, I liked this solution better. At any rate, I think this approach to state machines drove me towards a better design.
I think that is mostly attributable to the fact that the FSM forces one to follow FSA rules. (I would argue OOA/D requires one to follow the same rules but in a more subtle way so deviation and foot-shooting is easier.) It is nontrivial to build good state machines and get them to interact properly. But the effort is worth it because once one gets them right they tend to be very robust.
************* There is nothing wrong with me that could not be cured by a capful of Drano.
H. S. Lahman hsl@xxxxxxxxxxxxxxxxx Pathfinder Solutions -- Put MDA to Work http://www.pathfindermda.com blog: http://pathfinderpeople.blogs.com/hslahman (888)OOA-PATH
.
- Follow-Ups:
- Re: State Machine/Class Granularity
- From: Wavemaker
- Re: State Machine/Class Granularity
- From: Wavemaker
- Re: State Machine/Class Granularity
- References:
- State Machine/Class Granularity
- From: Wavemaker
- Re: State Machine/Class Granularity
- From: H. S. Lahman
- Re: State Machine/Class Granularity
- From: Wavemaker
- Re: State Machine/Class Granularity
- From: H. S. Lahman
- Re: State Machine/Class Granularity
- From: Wavemaker
- State Machine/Class Granularity
- Prev by Date: Re: OOP/OOD Philosophy
- Next by Date: Re: OOP/OOD Philosophy
- Previous by thread: Re: State Machine/Class Granularity
- Next by thread: Re: State Machine/Class Granularity
- Index(es):
Relevant Pages
|