Citation :
State
Synopsis
Encapsulate the states of an object as discrete objects, each extending a common superclass.
Context
Many objects are required to have a dynamically changing set of attributes called their state. Such objects are called stateful objects. An objects state will usually be one of a predetermined set of values. When a stateful object becomes aware of an external event, its state may change. The behavior of a stateful object is in some ways determined by its state.
For an example of a stateful object, suppose that you are writing a dialog for editing parameters of a program. The dialog will have buttons for specifying the disposition of changes you have made:
l The dialog will have an OK button that saves the parameter values in the dialog to both a file and the programs working values.
l The dialog will have a Save button that saves the parameter values only to a file.
l The dialog will have an Apply button that saves the parameter values only to the programs working values.
l The dialog will have a Revert button that restores the dialog values from the file.
It is possible to design such a dialog so that it is stateless. If a dialog is stateless, then it will always behave the same way. The OK button will be enabled whether or not you have edited the values in the dialog. The Revert button will be enabled even if the user has just reverted the dialog values to the contents of the file. If there are no other considerations, then designing this dialog to be stateless is satisfactory. In some cases, the dialogs stateless behavior may be a problem. Updating the values of the programs working values may be disruptive. Storing parameter values to a file might take an annoyingly long time if the file is on a remote shared file server. A way to avoid unnecessary saves to the file or unnecessary setting of the programs working parameter values is to make the dialog stateful so that it will not perform these operations when they are not useful. Instead, it will allow them only when updating the file or working values with values different from those that they already contain. Figure 8.20 is a state diagram showing the four states needed to produce this behavior.
Figure 8.20: Parameter dialog state diagram. To implement the state diagram in Figure 8.20, you can implement the classes shown in Figure 8.21.
Figure 8.21: DirtyState class diagram. Figure 8.21 shows four classes that correspond to the four states in the state diagram and their common superclass. The superclass, DirtyState, has a public method called processEvent. The processEvent method takes an event identifier as its argument and returns the next state. It determines the next state by calling the abstract nextState method. Each subclass of DirtyState overrides the nextState method in an appropriate way to determine the next state. The DirtyState class also has a static method called start. The start method gets things going by creating an instance of each subclass of the DirtyState class and returning the initial state. The start method also creates an instance of the DirtyState class and assigns its variables notDirty, fileDirty, paramDirty, and bothDirty to the corresponding subclass instances that it creates.
The DirtyState class defines a protected method called enter. A DirtyState objects enter method is called when it becomes the current state. It is called by the start method and the processEvent method. The enter method defined by the DirtyState class doesnt do anything. However, subclasses override the enter method to implement their entry actions. The DirtyState class defines some static constants. The constants identify event codes that are passed to the processEvent method.
Forces
J An objects behavior is determined by an internal state that changes in response to events.
J The organization of logic that manages an objects state should be able to scale up to many states without becoming one unmanageably large piece of code.
Solution
Figure 8.22 shows the basic class organization for the State pattern.
Figure 8.22: State class diagram. Here is an explanation of the roles classes play in the State pattern:
Context. Instances of classes in this role exhibit stateful behavior. Instances of Context determine their current state by keeping a reference to an instance of a concrete subclass of the State class. The subclass of the State class determines the state.
State. The State class is the superclass of all classes used to represent the state of Context objects. A State class defines these methods:
l A State objects enter method is called when the state that the object represents becomes the current state. The State class provides a default implementation of this method that does nothing. It is very common for subclasses of the State class to override this method.
l The start method performs any necessary initialization of state management objects and returns an object corresponding to the client objects initial state. Before it returns the State object that represents the initial state, it calls the State objects enter method.
l The nextState method is an abstract method that takes an argument that indicates the occurrence of an event and returns the next state. Each concrete subclass of State overrides the nextState method to determine the correct next state.
l A State objects exit method is called when the state that the object represents ceases to be the current state. The State class provides a default implementation of this method that does nothing. It is very common for subclasses of the State class to override this method.
l The processEvent method is a public method that takes an argument that indicates the occurrence of an event and returns the new current state. The processEvent method calls the nextState method. If the object that nextState method returns is a different object than the current State object, then there will be a new current state. In this case, the processEvent method calls the old current states exit method and then calls the new current states enter method.
l The methods operation1, operation2, and so on, implement operations that behave differently for each state. For example, if an object has states associated with it called On and Off, the implementation for an operation for the On state might do something, and the implementation for the Off state might do nothing. The design of these methods is an application of the Polymorphism pattern described in Patterns in Java, Volume 2.
The State class defines constants that are symbolic names for the event codes passed to the processEvent method. Unless a State class has instance variables, there is no need to have more than one instance of it. If there is only one instance of a concrete subclass of State, then the State class will have a static variable that refers to that instance. Implementations of the processEvent method return the instances referred to by those variables rather than create additional instances.
ConcreteState1, ConcreteState2, and so on. These are concrete subclasses of State. They must implement the operation1, operation2, and so on, methods in an appropriate way. They must also implement the nextState method to determine the appropriate next state for each event. ConcreteState classes may override the enter method and/or exit method to implement appropriate actions to be performed when entering or exiting a state.
Implementation
No class other than the State class needs to be aware of the subclasses of the State class. You can ensure that no class other than the State class is aware of its subclasses by declaring the subclasses of the State class as private member classes of the ContextState class.
Consequences
J The code for each state is in its own class. This organization makes it easy to add new states without unintended consequences. For this reason, the State pattern works well for small and large numbers of states.
J To clients of state objects, state transitions appear to be atomic. A client calls the current states processEvent method. When it returns, the client has its new state.
J A procedural implementation of stateful behavior typically involves multiple methods that contain switch statements or chains of if-else statements for dispatching to state-specific code. These can be large and difficult to understand. Using the State pattern eliminates these switch statements or chains of if-else statements. It organizes the logic in a more cohesive way that results in classes that have smaller methods.
J Using the State pattern results in fewer lines of code and fewer execution paths. This makes it easer to test a program using the White Box Testing pattern described in Patterns in Java, Volume 2.
J State objects that represent nonparametric states can be shared as singletons if there is no need to create a direct instance of the State class. In some cases, such as the example shown under the Context heading, there is a need to create an instance of the State class to provide a set of state objects with a way of sharing data. Even in those cases, for each subclass of the State class that represents a nonparametric state, there can be a single instance of that class associated with an instance of the State class.
l Using the State pattern eliminates the need for code in a method to dispatch to state-specific code. It does not eliminate state-specific switch statements that dispatch to a states handling of an event.
Related Patterns
Flyweight. You can use the Flyweight pattern to share state objects.
Mediator. The State pattern is often used with the Mediator pattern when implementing user interfaces.
Singleton. You can implement nonparametric states using the Singleton pattern.
Polymorphism. The design of state-specific operations implemented by concrete state classes follows the Polymorphism pattern discussed in Patterns in Java, Volume 2.
|