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
The GoF definition is easy to understand. It simply states that an object can change what it does based on its current state.
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 their current state, and you can define state-specific behaviors with different classes.
In this pattern, you think in terms of your application’s possible states, 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 according to the behavior of the current state. For example, suppose that you are watching a program on your television (TV). Now, if you press the Mute button on the TV’s remote control, there is a state change on your TV. But there is no change if the TV is already in a switched-off mode.
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 a scenario for a network connection, such as a TCP connection. An object can be in various states; for example, a connection might be just established, a connection might be closed, or the object is listening through the connection. When this connection receives a request from other objects, it responds according to its present state.
The functionalities of a traffic signal or television are other examples of the State pattern. For example, you can change the channel if the TV is already in switched-on mode. It does not respond to the channel change requests if it is in switched-off mode.
Computer-World Example
The TCP connection example can fit into this category. Consider another 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 it is busy with the maximum number of jobs that it can process at that time. This busy signal simply indicates that its total number of job-processing capabilities has been reached, and the new job request cannot be fulfilled immediately.
Implementation
Three concrete classes—On, Off, and Mute—implement this interface. The basic functionality can be described as follows. Initially, the TV is in the Off state. So, when you press the On button on the control panel, the TV moves to the On state, and then if you press the Mute button, it goes into the Mute state.
In this diagram, I did not mark any state as the final state, although in Figure 20-1, I switch to turn off the TV.
To make the design simpler, assume that if you press the Off (or Mute) button when the TV is in the Off state; or if you press the On button when the TV is in the On state; or if you press the Mute button when the TV is in Mute mode, there is 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 goes into Mute mode; and if press the Mute button again, the TV may return to On state. So, you may need to update your program logic accordingly.
I delegated the state behavior. For example, when you press ExecuteMuteButton(), the control invokes PressMuteButton(...) based on the current state of the television.
Let’s follow the class diagram now.
Class Diagram
Solution Explorer View
Demonstration
Output
Q&A Session
20.1 Can you elaborate on how this pattern works in a real-world scenario?
Psychologists have repeatedly documented the fact that human beings can perform their best when they are in a relaxed mood. In the reverse scenario, however, when their minds are filled with tension, they cannot produce great results. That is why they always suggest you working in a relaxed mood. So, the same work can be enjoyable or boring, depending on your current mood.
You can think about our demonstration example again. Suppose that you want to watch the live telecast of the winning moments of your favorite team. To watch and enjoy the moment, you need to power on the TV first. If the TV is not functioning properly at that moment and cannot be in the On state, you cannot enjoy the moment. So, if you want to enjoy the moment through your TV, the first criterion is that the TV should change its state from Off to On. The State pattern is helpful if you want to design a similar kind of behavior change in an object when its internal state changes.
20.2 In this example, you have considered only three states of a TV : On, Off, and Mute. There can be many other states; for example, there may be a state that deals with connection issues or different display conditions. Why have you ignored those issues?
The straightforward answer is I ignored those states to keep things simple. If the number of states increases significantly in the system, then it becomes difficult to maintain the system (and this is one of the key challenges associated with this design pattern). But if you understand this implementation, you can easily add any states that you want.
20.3 I noticed that the GoF represented a similar structure for both the State pattern and the Strategy pattern in their famous book. I’m confused by that.
Yes, the structures are similar, but you need to remember that their intents are different. When you use the Strategy pattern, you are getting a better alternative to subclassing. 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 state changes, its behavior also changes. So, the State pattern can be thought of as a dynamic version of the Strategy pattern.
You need to repeat these checks for different kinds of button presses (For example, for the ExecuteOffButton() and ExecuteMuteButton() method, you need to repeat these checks and program accordingly). So, if you do not think in terms of states, over time, handling different conditions with a lot of if-else is very challenging, and it can be difficult when the codebase continually grows.
20.4 How are you implementing the open/close principle in your example?
Each of these TV states is closed for modification, but you can add a new state to the TV class.
20.5 What are the common characteristics between the Strategy pattern and the State pattern?
The State pattern can be considered as a dynamic Strategy pattern. Both patterns promote composition and delegation.
20.6 It appears to me that these state objects are acting like singletons. Is this correct?
Yes, it’s a nice observation. The concrete subclasses of IPossibleStates do not contain any local state in this example, and as a result, in this application, only one state instance is working. Most of the time, this pattern acts similarly.
20.8 What are the pros and cons of the State design pattern?
You have seen that by following the open/close principle, you can add new states and extend a state’s behavior easily. Also, a state behavior can be extended without hassle. For example, in this implementation, you can add a new state and new behavior for a TV class without changing the TV class itself.
It reduces if-else statements. In other words, conditional complexity is reduced. (Refer to the answer to question 20.3.)
The State pattern is also known as Objects for States, so you can assume that more states need more code, and the obvious side effect is more difficult maintenance.
20.9 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 ignored that part to reduce some code size of the program. But yes, you can always start from an interface, for example, ITv, in which you can define the contracts.
20.10 In the TV class constructor, you are initializing the TV with an Off state. So, both states and the context class can trigger the state transitions?
Yes.