© Vaskaran Sarcar 2020
V. SarcarDesign Patterns in C#https://doi.org/10.1007/978-1-4842-6062-3_20

20. State Pattern

Vaskaran Sarcar1 
(1)
Garia, Kolkata, West Bengal, India
 

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

This example models the functionalities related to a TV, which has a control panel to support on, off, and mute operations. For simplicity, assume that at any given time, the TV is in any of these three states: On, Off, or Mute. The following shows an interface called IPossibleStates.
   interface IPossibleStates
    {
        //Users can press any of these buttons-On, Off or Mute
        void PressOnButton(TV context);
        void PressOffButton(TV context);
        void PressMuteButton(TV context);
    }

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.

Assume that if you press the Off button when the TV is in the Off state; 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. The TV can go into the Off state from the On state or the Mute state (when you press the Off button). Figure 20-1 is a state diagram that reflects all possible scenarios.
../images/463942_2_En_20_Chapter/463942_2_En_20_Fig1_HTML.jpg
Figure 20-1

Different states of a TV

Points to Remember
  • 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.

The TV has a control panel to support on, off, and mute operations. So, inside the TV class, there are three methods: ExecuteOffButton() , ExecuteOnButton() , and ExecuteMuteButton() as follows.
       public void ExecuteOffButton()
        {
           Console.WriteLine("You pressed Off button.");
            //Delegating the state behavior
            currentState.PressOffButton(this);
        }
        public void ExecuteOnButton()
        {
            Console.WriteLine("You pressed On button.");
            //Delegating the state behavior
            currentState.PressOnButton(this);
        }
        public void ExecuteMuteButton()
        {
            Console.WriteLine("You pressed Mute button.");
            //Delegating the state behavior
            currentState.PressMuteButton(this);
        }

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

Figure 20-2 shows the important parts of the class diagram.
../images/463942_2_En_20_Chapter/463942_2_En_20_Fig2_HTML.jpg
Figure 20-2

Class diagram

Solution Explorer View

Figure 20-3 shows the high-level structure of the program.
../images/463942_2_En_20_Chapter/463942_2_En_20_Fig3_HTML.jpg
Figure 20-3

Solution Explorer view

Demonstration

Here’s the complete implementation.
using System;
namespace StatePattern
{
    interface IPossibleStates
    {
        //Users can press any of these buttons-On, Off or Mute
        void PressOnButton(TV context);
        void PressOffButton(TV context);
        void PressMuteButton(TV context);
    }
    //Subclasses does not contain any local state.
    //Only one unique instance of IPossibleStates is required.
    /// <summary>
    /// Off state behavior
    /// </summary>
    class Off : IPossibleStates
    {
        public Off()
        {
            Console.WriteLine("---TV is Off now.--- ");
        }
        //TV is Off now, user is pressing On button
        public void PressOnButton(TV context)
        {
            Console.WriteLine("TV was Off.Going from Off to On state.");
            context.CurrentState = new On();
        }
        //TV is Off already, user is pressing Off button again
        public void PressOffButton(TV context)
        {
            Console.WriteLine("TV was already in Off state.So, ignoring this opeation.");
        }
        //TV is Off now, user is pressing Mute button
        public void PressMuteButton(TV context)
        {
            Console.WriteLine("TV was already off.So, ignoring this operation.");
        }
    }
    /// <summary>
    /// On state behavior
    /// </summary>
    class On : IPossibleStates
    {
       public On()
        {
            Console.WriteLine("---TV is On now.--- ");
        }
        //TV is On already, user is pressing On button again
        public void PressOnButton(TV context)
        {
            Console.WriteLine("TV is already in On state.Ignoring repeated on button press operation.");
        }
        //TV is On now, user is pressing Off button
        public void PressOffButton(TV context)
        {
            Console.WriteLine("TV was on.So,switching off the TV.");
            context.CurrentState = new Off();
        }
        //TV is On now, user is pressing Mute button
        public void PressMuteButton(TV context)
        {
            Console.WriteLine("TV was on.So,moving to silent mode.");
            context.CurrentState = new Mute();
        }
    }
    /// <summary>
    /// Mute state behavior
    /// </summary>
    class Mute : IPossibleStates
    {
        public Mute()
        {
            Console.WriteLine("---TV is in Mute mode now.--- ");
        }
        /*
        Users can press any of these buttons at this state-On, Off or Mute.TV is in mute, user is pressing On button.
        */
        public void PressOnButton(TV context)
        {
            Console.WriteLine("TV was in mute mode.So, moving to normal state.");
            context.CurrentState = new On();
        }
        //TV is in mute, user is pressing Off button
        public void PressOffButton(TV context)
        {
            Console.WriteLine("TV was in mute mode. So, switching off the TV.");
            context.CurrentState = new Off();
        }
        //TV is in mute already, user is pressing mute button again
        public void PressMuteButton(TV context)
        {
            Console.WriteLine(" TV is already in Mute mode, so, ignoring this operation.");
        }
    }
    /// <summary>
    /// TV is the context class
    /// </summary>
    class TV
    {
        private IPossibleStates currentState;
        public IPossibleStates CurrentState
        {
            get
            {
                return currentState;
            }
           /*
           Usually this value will be set by the class that implements the interface "IPossibleStates"
           */
            set
            {
                currentState = value;
            }
        }
        public TV()
        {
            //Starting with Off state
            this.currentState = new Off();
        }
        public void ExecuteOffButton()
        {
           Console.WriteLine("You pressed Off button.");
            //Delegating the state behavior
            currentState.PressOffButton(this);
        }
        public void ExecuteOnButton()
        {
            Console.WriteLine("You pressed On button.");
            //Delegating the state behavior
            currentState.PressOnButton(this);
        }
        public void ExecuteMuteButton()
        {
            Console.WriteLine("You pressed Mute button.");
            //Delegating the state behavior
            currentState.PressMuteButton(this);
        }
    }
    /// <summary>
    /// Client code
    /// </summary>
    class Client
    {
        static void Main(string[] args)
        {
            Console.WriteLine("***State Pattern Demo*** ");
            //TV is initialized with Off state.
            TV tv = new TV();
            Console.WriteLine("User is pressing buttons in the following sequence:");
            Console.WriteLine("Off->Mute->On->On->Mute->Mute->Off ");
            //TV is already in Off state
            tv.ExecuteOffButton();
  //TV is already in Off state, still pressing the Mute button
            tv.ExecuteMuteButton();
            //Making the TV on
            tv.ExecuteOnButton();
  //TV is already in On state, pressing On button again
            tv.ExecuteOnButton();
            //Putting the TV in Mute mode
            tv.ExecuteMuteButton();
     //TV is already in Mute, pressing Mute button again
            tv.ExecuteMuteButton();
            //Making the TV off
            tv.ExecuteOffButton();
            // Wait for user
            Console.Read();
        }
    }
}

Output

Here’s the output.
***State Pattern Demo***
---TV is Off now.---
User is pressing buttons in the following sequence:
Off->Mute->On->On->Mute->Mute->Off
You pressed Off button.
TV was already in Off state.So, ignoring this opeation.
You pressed Mute button.
TV was already off.So, ignoring this operation.
You pressed On button.
TV was Off.Going from Off to On state.
---TV is On now.---
You pressed On button.
TV is already in On state.Ignoring repeated on button press operation.
You pressed Mute button.
TV was on.So,moving to silent mode.
---TV is in Mute mode now.---
You pressed Mute button.
 TV is already in Mute mode, so, ignoring this operation.
You pressed Off button.
TV was in mute mode. So, switching off the TV.
---TV is Off now.---

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.

State patterns can also help you avoid a lot of if conditions in some contexts. For example, if a TV is in the Off state, it cannot go into the Mute state. From this state, it can move to the On state only. So, if you do not like the State design pattern, you may need to write the code like this.
class TV
{
//Some code before
public void ExecuteOnButton()
{
if(currentState==Off )
{
Console.WriteLine("You pressed On button. Going from Off to OnState");
//Some code after
}
if(currentState==On )
{
Console.WriteLine("You pressed On button. TV is already in on state. So, ignoring this opeation.");
//Some code after
}
else
{
Console.WriteLine("TV was on. Moving into mute mode now.");
}
//Some code after
}

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.7 Why are you using context as a method parameter? Can you avoid them in statements like this?
void PressOnButton(TV context);
Using the context, I’m saving states. Also, the concrete subclasses of IPossibleStates do not contain any local state. So, in this application, only one state instance is working. So, this construct helps you evaluate whether you are changing between states or you are already in the same state. Note the output. These contexts help you get output like the following.
"You pressed Mute button.
TV was already off.So, ignoring this operation."

20.8 What are the pros and cons of the State design pattern?

The advantages are as follows.
  • 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.)

There is a downside to using this pattern.
  • 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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.115.195