Blog root page
previous post in category
In the post on DbC I indicated that one can construct object state machines without worrying about flow of control in the overall problem solution. While not completely true, it is close enough for horseshoes because of the way we abstract the problem space when doing OO development.
It is crucial to OO development that one abstract intrinsic responsibilities for objects from underlying problem space entities. It is in the nature of the notion of intrinsic that those responsibilities should be context-independent. So, to that extent my statement is quite correct; we should be able to design object state machines without knowing anything explicit about the sequence of operations necessary to solve the customer's problem.
Where the hedge lies is that one only abstracts the properties one needs to solve the problem in hand. In addition, one tailors the abstraction to suit the problem in hand. To that extent one does have one eye on the problem to be solved and some general notion of the collaborations necessary to solve the problem. However, one doesn't need to know anything specific about solution flow of control -- it is quite feasible to design states, transitions, and actions without generating events in actions or assigning specific events to actions.
One does that by capturing the notion of an intrinsic object life cycle in the state machine. The basic notion behind an object life cycle is that the object is born (instantiated), wanders through some series of life stages, and dies. Like a person who may cycle through multiple marriages and divorces, the life of an object may have closed cycles within the overall linear born/die progression. Typically there are some structural rules about the order in which an object traverses its stages. Thus a person cannot changes state to a Divorced stage unless they were in a Married stage.
The mapping of an object life cycle ot a state machine is pretty straight forward at this level. A stage of life becomes a state and the valid changes between stages are mapped as transitions between states. Recognizing such life cycles is pretty easy for something like a light bulb, whose stages might be {OFF, ON, BURNED_OUT}. Alas, problem space entities are often more subtle than that and life cycle stages are more difficult to recognize.
However, if we think in terms of state machines we have an important clue. Since a state is a condition where a particular suite of rules and policies are relevant, we can define states around the object's behavior responsibilities. (At the Class Diagram level we already defined our objects, their mission, and the nature of their responsibilities in the problem context.) If we have logically indivisible behaviors we can associate them with states. Often this leads to discovering the intrinsic life cycle that is relevant to the problem in hand. In effect we are tailoring the life cycle to the problem in hand.
For example, suppose we are writing the controller software for an automatic garage door opener. We have identifier the Door as a central entity that will anthropomorphize behaviors. Initially the obvious states are {OPEN, CLOSED}. However, we have also identified a behavior that occurs when the electric eye detects something in the path of the door. If the door is already fully opened or closed, that behavior is irrelevant. It only has meaning if the door is in the act of closing. So we recognize that we need an additional state {OPEN, CLOSED, CLOSING} that is a condition relevant to the electric eye behavior.
A similar argument can be applied to receiving a clicker event. If the door is already open or closed, it toggles to the other state. But opening and closing take finite time so what does one do if another click is received while the door is in the process of closing or opening? Most requirements specify the door should reverse direction. But to accommodate that we also need an OPENING state. So after considering the behaviors needed to solve the problem and understanding the realities of the problem space (finite state transition time), we arrive at:
at stop
+-----------------------------------------------------+
| |
V click obstruction |
[Closed] ---------------------> [Opening] <-----------+ |
/ | ^ | |
at stop / | | | |
+--------------------------/ | | | |
| | | | |
| click | | click | |
| | | | |
V V | | |
[Open] -----------------------> [Closing] ------------+ |
click | |
| |
+---------------------+
So we have two new life cycle states developed from the object responsibilities that were not immediately obvious initially. However, as we review this state machine within the context of the problem we detect that it is not quite correct. The problem lies in recognizing that there is an obstruction. According to this model, it there is another click after the "obstruction" event is received, the door will start closing again and crush the baby carriage.
My point in bringing this up is that the analysis that leads to the solution, including recognizing and dealing with an incorrect preliminary state machine, can be done with no specific knowledge of the context. Everything I have described results from an analysis of the intrinsic responsibilities that we already identified for this object. The only external information we need to understand is the we must properly respond to clicker, electric eye, and stop detection events. The responses themselves and the state machine structure are all defined intrinsically in the nature a garage door opener.
For those curious about one possible solution:
at stop
+-------------------------------------------+
| |
V click |
[Closed] ---------------------> [Opening] |
/ | ^ |
at stop / | | |
+--------------------------/ | | |
| | | |
| click | | click |
| | | |
V V | |
[Open] -----------------------> [Closing] ------+
^ click |
| |
| unobstructed |
| |
| obstructed |
[Obstructed] <----------------------+
Where the action for the OBSTRUCTED state is to order the hardware to open the door. Any "click" or "at Stop" events are ignored in that state. Since OPEN and CLOSED don't actually do anything except wait for a click event, everything Just Works.
[FWIW, I would be suspicious of this solution because it suggests a lack of cohesion in Door. There are two distinct groups of responsibilities: talking to the hardware about opening and closing vs. providing an appropriate response to the electric eye. I would be inclined to delegate the response to the electric eye to some other object. Object state machines are surprisingly easy to used despite their asynchronous nature -- but only if one keeps them simple, which is consistent with the OO notion of cohesion. However, this example was aimed at illustrating different issues.]
Blog root page
previous post in category