This chapter covers the state pattern.
GoF Definition
Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.
Concept
Suppose that you are dealing with a large-scale application where the codebase is rapidly growing. As a result, the situation becomes complex and you may need to introduce lots of if-else blocks/switch statements to guard the various conditions. The state pattern fits in such a context. It allows your objects to behave differently based on the current state, and you can define state-specific behaviors with different classes.
So, in this pattern, you start thinking in terms of possible states of your application, and you segregate the code accordingly. Ideally, each of the states is independent of other states. You keep track of these states, and your code responds as per the behavior of the current state. For example, suppose that you are watching a television (TV) program/show. If you press the mute button on the TV’s remote control, you notice a state change in your TV. But you cannot notice any change if the TV is already turned off.
So, the basic idea is that if your code can track the current state of the application, you can centralize the task, segregate your code, and respond accordingly.
Real-World Example
Consider the scenario of a network connection—a TCP connection. An object can be in various states; for example, a connection might already be established, the connection might be closed, or the object already started listening through the connection. When this connection receives a request from other objects, it responds as per its present state.
The functionalities of a traffic signal or a television (TV) can also be considered in this category. For example, you can change channels if the TV is already in a switched-on mode. It will not respond to the channel change requests if it is in a switched-off mode.
Computer-World Example
Suppose that you have a job-processing system that can process a certain number of jobs at a time. When a new job appears, either the system processes the job, or it signals that the system is busy with the maximum number of jobs that it can process at one time. In other words, the system sends a busy signal when its total number of job-processing capabilities has been reached.
Note
In the javax.faces.lifecycle package, there is class called Lifecycle. This class has a method called execute(FacesContext context), in which you may notice an implementation of the state design pattern. FacesServlet can invoke the execute method of a LifeCycle and a LifeCycle object communicates with different phases (states).
Illustration
The following implementation models the functionalities of a television and its remote control. Suppose that you have a remote control to support the operations of a TV. You can simply assume that at any given time, the TV is in either of these three states: On, Off, or Mute. Initially, the TV is in the Off state. When you press the On button on the remote control, the TV goes into the On state. If you press the Mute button, it goes into the Mute state.
You can assume that if you press the Off button when the TV is already in the Off state, or if you press the On button when the TV is already in the On state, or if you press the Mute button when the TV is already in Mute mode, there is no state change for the TV.
Note
In this diagram, I have not marked any state as the final state, though in this illustration, at the end, I am switching off the TV. To make the design simple, I assume that if you press the Off button when the TV is already in the Off state, or if you press the On button when the TV is already in On state, or if you press the Mute button when the TV is already in Mute mode, there will be no state change to the TV. But in the real-world, a remote control may work differently. For example, if the TV is currently in the On state and you press the Mute button, the TV can go to Mute mode, and then if press Mute button again, the TV may come back to the On state again. So, you may need to put additional logic to support this behavior.
Key Characteristics
For a state-specific behavior, you have separate classes. For example, here you have classes like On, Off, and Mute.
The TV class is the main class here (the word main does not mean that it includes the main() method) and the client code only talks to it. In design pattern terms, TV is the context class here.
Operations defined in the TV class are delegating the behaviour to the current state’s object implementation.
PossibleState is the interface that defines the methods/operations that are called when you own an object. On, Off, and Mute are concrete states that implement this interface.
States are triggering state transitions (one state to another state) themselves.
Class Diagram
Package Explorer View
Implementation
Output
Q&A Session
- 1.
Can you elaborate how this pattern is useful with another real-world scenario?
Psychologists repeatedly documented the fact that human beings can perform their best when they are in a relaxed mode and they are free of tension but in the reverse scenario, when their minds are filled with tension, they cannot produce great results. That is why psychologists always suggest that you should work in relaxed mood. You can relate this simple philosophy with the TV illustration. If the TV is on, it can entertain you; if it is off, it cannot. Right? So, if you want to design similar kinds of behavior changes of an object when it’s internal state changes, this pattern is useful.
Apart from this example, you can consider the scenario where a customer buys an online ticket and in some later phase he/she cancels it. The refund amount may vary with different conditions; for example, the number of days before you can cancel the ticket.
- 2.
In this example, you have considered only three states of a TV: On, Off, or Mute. There are many other states, for example, there may be a state that deals with connection issues or display conditions. Why have you ignored those?
The straightforward answer is to represent simplicity. If the number of states increases significantly in the system, then it becomes difficult to maintain the system (and it is one of the key challenges associated with this design pattern). But if you understand this implementation, you can easily add any states you want.
- 3.
I noticed that the GoF represented a similar structure for both the state pattern and the strategy pattern in their famous book. I am confused to see that.
Yes, the structures are similar, but you need to note that the intents are different. Apart from this key distinction, you can simply think like this: with a strategy pattern provides a better alternative to subclassing. On the other hand, in a state design pattern, different types of behaviors can be encapsulated in a state object and the context is delegated to any of these states. When a context’s internal states change, its behavior also changes.
State patterns can also help us avoid lots of if conditions in some contexts. (Consider our example once again. If the TV is in the Off state, it cannot go to the Mute state. From this state, it can move to the On state only.) So, if you do not like state design pattern, you may need to code like this for a On button press.class TV{//Some code beforepublic void pressOnButton(){if(currentState==Off ){System.out.println (" You pressed Onbutton. Going from Off to OnState");//Do some operations}if(currentState==On ){System.out.println (" You pressed On button. TV is already in On state");}//TV presently is in mute modeelse{System.out.println (" You pressed On button . Going from Mute mode to On State");}//Do some operations}Notice that you need to repeat these checks for different kinds of button presses. (For example, for the pressOffButton() and pressMuteButton() methods, you need to repeat these checks and perform accordingly.)
If you do not think in terms of states, if your code base grows, maintenance becomes difficult.
- 4.
How are you supporting the open-close principle in our implementation?
Each of these TV states are closed for modification, but you can add brand-new states to the TV class.
- 5.
What are the common characteristics between the strategy pattern and the state pattern?
Both can promote composition and delegation.
- 6.
It appears to me that these state objects are acting like singletons. Is this correct?
Yes. Most times they act in this way.
- 7.Can you avoid the use of “contexts” in the method parameters. For example, can you avoid them in the following statements?void pressOnButton(TV context);....
If you do not want to use the context parameter like this, you may need to modify the implementation. To give a quick overview, I am presenting the modified Package Explorer view with a modified implementation only.
One of the key changes in the following implementation can be seen in the class TV. The TV() constructor is initialized with all possible state objects, which are used for the change of states in later phases. The getter methods are invoked for this purpose. Consider the following implementation.
Modified Package Explorer View
In this case, all three possible states have similar components. So, to keep the diagram short, I am showing only one of them in the following Package Explorer view.
Modified Implementation
Modified Output
- 8.
In these implementations, TV is a concrete class. Why are you not programming to interface in this case?
I assume that the TV class is not going to change, and so I ignored that part to reduce some code size of the program. But yes, you can always start from an interface in which you can define the contracts.
- 9.
What are the pros and cons of a state design pattern?
ProsYou have already seen that following the open/close principle, you can easily add new states and new behaviors. Also, a state behavior can be extended without hassle. For example, in this implementation, you can add a new state and a new behavior for a TV class without changing the TV class itself.
Reduces the use of if-else statements (i.e., conditional complexity is reduced. (Refer to the answer in question 3).
ConsThe state pattern is also known as objects for states. So, you can assume that more states need more codes, and the obvious side effect is difficult maintenance for you.
- 10.
In the TV class constructor, you are initializing the TV with an Off state. So, can both the states and the context class trigger the state transitions?
Yes.