Sunday, August 06, 2006

Intersecting Parallel Lines

The container or nesting approach will work very well if there are only two or three classes that are to be nested.  These two or three classes might be able to handle six or seven stages, although this does seem to violate the separation of responsibilities principle at least a little bit.  If the number of classes got to five or six, I might start to sweat about how I would justify the design in a design review.  At seven or more, I would definitely be looking for a different way to accomplish the goal.  Another issue to consider is whether the stages are linear; that is, does the incoming data always go through the same set of stages?  It is probably less likely, but still possible that there could be two or more valid sequences of stages on the journey to complete and valid data.  Different sub-cultures in your user population may start filling data in a different order.  If that is the case, the application might accomodate those different patterns of data entry.

All of this leads me to the second approach, which I call a conversion technique.  Remember that we want to present a unified interface to the client software.  I take that to mean that the client holds a reference to a single object that implements all of the methods (e.g.,  properties and status functions).  I will call this the Controller class.  Let’s step back for a moment and take an inventory of the functions that we need to handle. 

  • First, we need to perform the logic of the various stages.
  • Second, we need to dispatch the method invocations against the Controller object to the appropriate Stage logic.  In this sense, the Controller is acting as a proxy for the currently active stage.
  • Third, we need to decide if it is time to switch from the current stage to a subsequent stage.  This is not the function of deciding what the next stage is, just recognizing that the decision should be made.
  • Fourth, we need to determine what the next stage should be (once we have recognized the need to switch stages.)
  • Fifth, we need to convert the data from the current stage into the new stage that, in turn, becomes the next current stage object.

As always, the principle of encapsulation tells us that we can pretty much do anything we want “under the covers” in this Controller class.   We could make a single class that handle all of this logic, but what would be the fun in doing that?  Since the main reason that we are considering the “conversion” approach is because of the large number of different stages, I would want to place the logic of each stage in a separate class and define a common interface for each of these stage classes.  This would be either an abstract parent class with a derived child stage class for each stage or an actual interface re-implemented in each of the stage classes.   Which way that you go depends upon how much common logic can reasonably be allocated to the a abstract parent class, the code generation tools that are available, and personal preferences.

The Controller class holds a reference to an inner “current” stage object; The Controller class calls methods on this inner stage object; at certain points the reference to this “current” stage reference changes to a different stage object. The Controller class might be as simple as a proxy class that delegates each call to  the current  inner stage object.  In that case, we might implement the “stage object” interface on the Controller.  It is even possible that the Controller is also derived from the parent stage class; keep in mind, however, that at any point in time there must be two distinct instances of objects from this hierarchy: the Contoller (a reference of which is held by the client) and the current stage object (a reference to which is held by the Controller).

We have now made decisions on first and second functional requirements.  The next thing to work on is the third functinal requirement: the triggering mechanism.  We have two issues to consider: where to place the logic to test for the trigger and when to apply that test. 

  • At what point should we decide to test for the possibility of a change of state?  At one end of the spectrum, we could make the test each and every time that any change was made to the data.  At the other end of the spectrum, we could make the test only when there was an indication that the data should be saved.  In the middle, between these two extremes, we might only test when certain values are changed.  An awful lot of this depends upon the specifics of the problem and there’s not a lot more that I can say at this point. 
  • Where should the the test logic for the trigger reside?  Our choices here are the controller and individual stage classes.  If we decide that we want to test every time that a change is made, it might seem that the logical place for the logic would be in the controller.  This is an area that I would want to think about very carefully because, it is very likely that each of the stage objects would have to be involved in deciding whether it was time for a stage transition.  There’s also a question about how we would go about communicating that the stage object was ready to transition.  The stage object could raise an event that is handled by the Controller object, but keep in mind that this would be its last act before dying.  I would want take special care to deal with the event linkages so that the dead stage object could be garbage collected.  While it is possible I might go in a different direction, the most likely combination would be to include a method in each of the stage classes that would determine if it was ready for transition and add logic to each of the “change” methods in the controller class to inquire of the stage class after it had been modified if it were ready to transition.  That way I could write optimized code in each of the stages that could quickly detect conditions that would prevent a transition and exit, returning a “not ready for transition” status.  This just seems to me to be the most “uncluttered” approach.

We have to decide where to put the logic that controls the transition from one stage to the next.  There seem to be three distinct possibilities here:

  • We could put the logic in the individual stage class.  Quite frankly, this doesn’t feel right to me.  This might be acceptable if the transitions were very simple, a string of pearls type of arrangement.  The problem I have with this is that the logic for making the transitions is spread out over all of the various stage classes.  It does not seem to be any particular advantage to placing it in the individual stage classes and does seem to make it more difficult to find and alter that logic in the future.
  • We could put the logic in the controller class.  This has advantage of placing all the logic in a single place where it can be easily found and changed.  This seems to be much more acceptable than placing a logic in the individual stage classes.  Earlier I indicated that we might want the Controller to be inherited or derived from a parent stage class.  It seems to me that if the Controller were inherited from the parent stage class, we might well want to not place the transition logic within the controller.  I’m not quite sure why I feel this way but it just does not seem that those two things ought to be in the same class.
  • We could put the logic in a brand-new class that deals only with the issue of making transitions.  Let me call this the Transition class.  The Controller class would hold a reference to this abstract class and invoke what is probably the only method on the interface each time it decided that a transition was necessary.  The method would return a new stage class representing the new current stage.  This approach also has the advantage of allowing the advancement strategy to the control at runtime, either by a configuration parameter or by some dynamic appraisal of the context of the data entry process.

Now at this point, little bells going off in the back of my head saying, “the Transition class is very cute and perhaps elegant, but could well be a lot of extra added complexity for no particular benefit.”  It may well be that you never ever have a need for changing the pattern of transition for the stages.  If that’s the case, the Tansition class, while not entirely stupid, seems to be a lot of extra added overhead.  I would probably end up putting the logic in the Controller class to begin with but try to package that logic in such a way that I could easily refactor it to a Transition class in the future.

Our final concern is the conversion of the data in one stage object to create a new and different stage object.  you could put this in the same place where the transition decision logic is (either in the controller class or in the transition class) or we could put it in each of the individual stage classes.  If we went the way of the individual stage classes, I would probably define a constructor in each of the classes that would accept an instance of a class derived from the parent state class and logic in that constructor to copy the data.  The logic in the individual stage class constructors might be quite different depending upon the stage.  However, it might well be the case that this logic is the same for every such constructor and I might want to think about moving it to the logic that is handling the transition logic.  [I have this image of a creepy-crawley thing with eight legs and compound eyes sucking out the goodness of the the stage object which is just about to die; that’s a sort of image still get your juices flowing.]

The conversion approach adds a fair amount of complexity.  You would need to think about this for a while before deciding to venture into this particular territory.  Even after making the decision to enter this territory, there are a lot of different paths in this particular approach that you might take.  There are a lot of design decisions that have to be made.  None of them are particularly earthshaking, but it takes time to think about them and to capture your rationale for making one choice over the other so that those who follow behind you will understand why it is you took this particular path and not the others.


Post a Comment

Links to this post:

Create a Link

<< Home