Remembering which of a finite number of states an object is in and effecting changes of state are typical responsibilities of a Moment-Interval class. The status attribute, and complete and cancel operations of the Moment-Interval class archetype remind us of these responsibilities. The complete and cancel operations in particular remind us that we need a means of ending the instant or duration of time that a Moment-Interval represents.
Figure 1: Moment-Interval with typical attributes and operations representing state management responsibilities
For simple life-cycles a simple attribute might be all that is needed to remember the state. For example, a simple Sale object that is either in progress or complete can easily be modelled by a simple attribute whose value is modified by a complete() operation.
However, the life-cycle of a Moment-interval can be much more complex. The life-cycle for a Moment-Interval representing an Expense Claim, for example, might need several iterations through an approval process with different levels of approval being needed for different kinds or size of expense. In these circumstances the Gang of Four State pattern [GoF] starts to like a more appropriate approach than a simple attribute.
The Gang of Four State pattern is particularly useful when an object has to significantly change its behaviour depending on which state or mode it is in. The pattern suggests defining a class for each state that the object can be in. Each state class implements a common interface or inherits from the same abstract class. The object whose life-cycle is to be controlled, holds a reference to the state object that models its current state and delegates it's behaviour to that state object. A change in state switches the referenced state object to an object of another state class. This new class is then delegated to instead therefore changing the response of the main class depending on which state class is currently referenced. The state pattern adds more classes to a system but avoids the error-prone, repetitive use of complex if then else or switch case statements that provide alternative behaviour depending on the value of a status attribute.
Figure 2: Moment-Interval class with status attribute replaced by State pattern interface and classes
In applying the State pattern we have a number of design options to choose from. If the amount of different information that needs to be held for each specific state is low, we can choose to store the information in the main Moment-Interval object rather than in the individual state objects. This means we have no information stored in the objects representing states. In other words, despite it sounding like an oxymoron, we have stateless state objects that can be shared between multiple objects of the Moment-Interval class. Indeed, the state classes could end up being examples of the Singleton design pattern. This keeps the overhead of creating and holding additional objects in memory to a minimum.
However, stateless state objects are not very elegant if there are significant amounts of different data that need to be held for each state. In these cases, storing all this information in the main Moment-Interval class means that this class can become overly large. It can also mean that Moment-Interval objects allocate space for numerous attributes that are not used because the object never enters the particular states that require those attributes to be populated. However, there is another drawback to consider if we want to store information in the state objects.
Normally objects of the context class in the State pattern, the Moment-Interval class in our case, keep a reference only to the state object that represents the current state. Previous state objects are forgotten about. In other words, no history of state changes is kept. If we are keeping information that is specific to a state in the state objects we lose that information when the Moment-Interval changes to a new state. This might not be a big problem in a real-time or industrial system but throwing away meaningful data in business systems can, at best, lead to missed opportunities to use that data for performance or assessment reasons. At worst, we might be failing to keep information needed for legal reasons.
Fortunately, it is a reasonably easy addition to the pattern to keep an ordered list of the previous state objects. However, we have now moved a long way from the idea of a few additional stateless objects shared among all the objects of our Moment-Interval class but, assuming there are genuine requirements to keep information about each state the Moment-Interval finds itself in, the memory and performance overhead of creating and holding the extra objects is likely to be acceptable.
Figure 3: Moment-Interval Class that holds a history of previous state objects in addition to the current state
If a Moment-Interval object is holding the list of previous states it has been in, it is also likely that we would want to keep the time that the object was in each state. Therefore, we need to add date or time attributes to each state class.
Figure 4: State classes with date-time information
Now if we step back and consider our state classes we notice that they now represent intervals of time that we are interested in recording for either business or legal reasons. In other words, our state classes are very close to being Moment-Interval classes themselves. In fact, it could be argued that the list of previous states models a sequence of subsequent Moment-Intervals. It is true that the state classes are very unlikely to have their own status and priority responsibilities but that is OK because they are only suggested responsibilities after all.
The similarities between a history of states and a sequence of subsequent Moment-Intervals becomes even more apparent in cases where we are only interested in keeping a history of the more important states that represent significant milestones in the life-cycle of the Moment-Interval. For example, in the case of the expense claim we might not be too concerned with capturing every time the claim was sent back to the submitter for correction, corrected and resubmitted. We might only be interested in the final submission and approval states. We may also want to frequently access these particular individual states and, therefore, rather than hold a collection of states that we need to search through to find the right one, we will hold references directly to those particular states.
The other difference between a sequence of Moment-intervals and the design state pattern that should be considered is that usually a Moment-Interval provides access to its subsequent Moment-Intervals but the state objects in the state design pattern are only accessed indirectly via the context object. Again these two cases represent two extremes and a mixture of delegation by a content class and direct access to the subsequent Moment-Intervals can be used to good effect. For complex Moment-Intervals forcing them to act as a facade for all the subsequent Moment-intervals or states can lead to the public interface of the Moment-Interval simply growing too big and lack cohesion. However, hiding certain subsequent behaviour behind a Moment-Interval class can reduce the number of classes that client code needs to know about.
The State design pattern and sequence of subsequent Moment-Intervals provide two ways of looking at the life-cycle of a concept. Comparing and contrasting the two approaches provides us with a number of design options and tradeoffs from which we can pick the ones that are most applicable for our system.