© Vaskaran Sarcar 2019
Vaskaran SarcarJava Design Patternshttps://doi.org/10.1007/978-1-4842-4078-6_20

20. State Pattern

Vaskaran Sarcar1 
(1)
Bangalore, Karnataka, 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

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.

The TV can go into the Off state from the On state if you press the Off button, or it goes into a Mute state if you press the Mute button. Figure 20-1 shows the state diagram that reflects all of these possible scenarios.
../images/395506_2_En_20_Chapter/395506_2_En_20_Fig1_HTML.jpg
Figure 20-1

Different states of a 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

The key characteristics of the following implementations are as follows.
  • 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

Figure 20-2 shows the class diagram.
../images/395506_2_En_20_Chapter/395506_2_En_20_Fig2_HTML.jpg
Figure 20-2

Class diagram

Package Explorer View

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

Package Explorer view

Implementation

Here’s the implementation.
package jdp2e.state.demo;
interface PossibleState
{
    void pressOnButton(TV context);
    void pressOffButton(TV context);
    void pressMuteButton(TV context);
}
//Off state
class Off implements  PossibleState
{
    //User is pressing Off button when the TV is in Off state
    @Override
    public void pressOnButton(TV context)
    {
        System.out.println("You pressed On button. Going from Off to On state.");
        context.setCurrentState(new On());
        System.out.println(context.getCurrentState().toString());
    }
    //TV is Off already, user is pressing Off button again
    @Override
    public void pressOffButton(TV context)
    {
        System.out.println("You pressed Off button. TV is already in Off state.");
    }
    //User is pressing Mute button when the TV is in Off state
    @Override
    public void pressMuteButton(TV context)
    {
        System.out.println("You pressed Mute button.TV is already in Off state, so Mute operation will not work.");
    }
    public String toString()
    {
        return " **TV is switched off now.**";
    }
}
//On state
class On implements PossibleState
{
    //TV is On already, user is pressing On button again
    @Override
    public void pressOnButton(TV context)
    {
        System.out.println("You pressed On button. TV is already in On state.");
    }
    //User is pressing Off button when the TV is in On state
    @Override
    public void pressOffButton(TV context)
    {
        System.out.println("You pressed Off button.Going from On to Off state.");
        context.setCurrentState(new Off());
        System.out.println(context.getCurrentState().toString());
    }
    //User is pressing Mute button when the TV is in On state
    @Override
    public void pressMuteButton(TV context)
    {
        System.out.println("You pressed Mute button.Going from On to Mute mode.");
        context.setCurrentState(new Mute());
        System.out.println(context.getCurrentState().toString());
    }
    public String toString()
    {
        return " **TV is switched on now.**";
    }
}
//Mute state
class Mute implements PossibleState
{
    //User is pressing On button when the TV is in Mute mode
    @Override
    public void pressOnButton(TV context)
    {
        System.out.println("You pressed On button.Going from Mute mode to On state.");
        context.setCurrentState(new On());
        System.out.println(context.getCurrentState().toString());
    }
    //User is pressing Off button when the TV is in Mute mode
    @Override
    public void pressOffButton(TV context)
    {
        System.out.println("You pressed Off button. Going from Mute mode to Off state.");
        context.setCurrentState(new Off());
        System.out.println(context.getCurrentState().toString());
    }
    //TV is in mute mode already, user is pressing mute button again
    @Override
    public void pressMuteButton(TV context)
    {
        System.out.println("You pressed Mute button.TV is already in Mute mode.");
    }
    public String toString()
    {
        return " **TV is silent(mute) now**";
    }
}
class TV
{
    private PossibleState currentState;
    public TV()
    {
        //Initially TV is initialized with Off state
        this.setCurrentState(new Off());
    }
    public PossibleState getCurrentState()
    {
        return currentState;
    }
    public void setCurrentState(PossibleState currentState)
    {
        this.currentState = currentState;
    }
    public void pressOffButton()
    {
        currentState.pressOffButton(this);//Delegating the state
    }
    public void pressOnButton()
    {
        currentState.pressOnButton(this);//Delegating the state
    }
    public void pressMuteButton()
    {
        currentState.pressMuteButton(this);//Delegating the state
    }
}
//Client
public class StatePatternExample {
    public static void main(String[] args) {
        System.out.println("***State Pattern Demo*** ");
        //Initially TV is Off.
        TV tv = new TV();
        System.out.println("User is pressing buttons in the following sequence:");
        System.out.println("Off->Mute->On->On->Mute->Mute->Off ");
        //TV is already in Off state.Again Off button is pressed.
        tv.pressOffButton();
        //TV is already in Off state.Again Mute button is pressed.
        tv.pressMuteButton();
        //Making the TV on
        tv.pressOnButton();
        //TV is already in On state.Again On button is pressed.
        tv.pressOnButton();
        //Putting the TV in Mute mode
        tv.pressMuteButton();
        //TV is already in Mute mode.Again Mute button is pressed.
        tv.pressMuteButton();
        //Making the TV off
        tv.pressOffButton();
    }
}

Output

Here’s the output.
***State Pattern Demo***
User is pressing buttons in the following sequence:
Off->Mute->On->On->Mute->Mute->Off
You pressed Off button. TV is already in Off state.
You pressed Mute button.TV is already in Off state, so Mute operation will not work.
You pressed On button. Going from Off to On state.
    **TV is switched on now.**
You pressed On button. TV is already in On state.
You pressed Mute button.Going from On to Mute mode.
    **TV is silent(mute) now**
You pressed Mute button.TV is already in Mute mode.
You pressed Off button. Going from Mute mode to Off state.
    **TV is switched off now.**

Q&A Session

  1. 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. 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. 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 before
    public 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 mode
    else
     {
      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. 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. 5.

    What are the common characteristics between the strategy pattern and the state pattern?

    Both can promote composition and delegation.

     
  6. 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. 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.

Figure 20-4 shows the modified high-level structure of the program.
../images/395506_2_En_20_Chapter/395506_2_En_20_Fig4_HTML.jpg
Figure 20-4

Modified Package Explorer View

Modified Implementation

Here is the modified implementation.
package jdp2e.state.modified.demo;
interface PossibleStates
{
    void pressOnButton();
    void pressOffButton();
    void pressMuteButton();
}
class Off implements  PossibleStates
{
    TV tvContext;
    //Initially we'll start from Off state
    public Off(TV context)
    {
        //System.out.println(" TV is Off now.");
        this.tvContext = context;
    }
    //Users can press any of these buttons at this state-On, Off or Mute
    //TV is Off now, user is pressing On button
    @Override
    public void pressOnButton()
    {
        System.out.println(" You pressed On button. Going from Off to On state");
        tvContext.setCurrentState(tvContext.getOnState());
        System.out.println(tvContext.getCurrentState().toString());
    }
    //TV is Off already, user is pressing Off button again
    @Override
    public void pressOffButton()
    {
        System.out.println(" You pressed Off button. TV is already in Off state");
    }
    //TV is Off now, user is pressing Mute button
    @Override
    public void pressMuteButton()
    {
        System.out.println(" You pressed Mute button.TV is already in Off state, so Mute operation will not work.");
    }
    public String toString()
    {
        return " **TV is switched off now.**";
    }
}
class On implements PossibleStates
{
    TV tvContext;
    public On(TV context)
    {
        //System.out.println(" TV is On now.");
        this.tvContext = context;
    }
    //Users can press any of these buttons at this state-On, Off or Mute
    //TV is On already, user is pressing On button again
    @Override
    public void pressOnButton()
    {
        System.out.println("You pressed On button. TV is already in On state.");
    }
    //TV is On now, user is pressing Off button
    @Override
    public void pressOffButton()
    {
        System.out.println(" You pressed Off button.Going from On to Off state.");
        tvContext.setCurrentState(tvContext.getOffState());
        System.out.println(tvContext.getCurrentState().toString());
    }
    //TV is On now, user is pressing Mute button
    @Override
    public void pressMuteButton()
    {
        System.out.println("You pressed Mute button.Going from On to Mute mode.");
        tvContext.setCurrentState(tvContext.getMuteState());
        System.out.println(tvContext.getCurrentState().toString());
    }
    public String toString()
    {
        return " **TV is switched on now.**";
    }
}
class Mute implements PossibleStates
{
    TV tvContext;
    public Mute(TV context)
    {
        this.tvContext = context;
    }
    //Users can press any of these buttons at this state-On, Off or Mute
    //TV is in mute, user is pressing On button
    @Override
    public void pressOnButton()
    {
        System.out.println("You pressed On button.Going from Mute mode to On state.");
        tvContext.setCurrentState(tvContext.getOnState());
        System.out.println(tvContext.getCurrentState().toString());
    }
    //TV is in mute, user is pressing Off button
    @Override
    public void pressOffButton()
    {
        System.out.println("You pressed Off button. Going from Mute mode to Off state.");
        tvContext.setCurrentState(tvContext.getOffState());
        System.out.println(tvContext.getCurrentState().toString());
    }
    //TV is in mute already, user is pressing mute button again
    @Override
    public void pressMuteButton()
    {
        System.out.println(" You pressed Mute button.TV is already in Mute mode.");
    }
    public String toString()
    {
        return " **TV is silent(mute) now**";
    }
}
class TV
{
    private PossibleStates currentState;
    private PossibleStates onState;
    private PossibleStates offState;
    private PossibleStates muteState;
    public TV()
    {
        onState=new On(this);
        offState=new Off(this);
        muteState=new Mute(this);
        setCurrentState(offState);
    }
    public PossibleStates getCurrentState()
    {
        return currentState;
    }
    public void setCurrentState(PossibleStates currentState)
    {
        this.currentState = currentState;
    }
    public void pressOffButton()
    {
        currentState.pressOffButton();
    }
    public void pressOnButton()
    {
        currentState.pressOnButton();
    }
    public void pressMuteButton()
    {
        currentState.pressMuteButton();
    }
    public PossibleStates getOnState()
    {
        return onState;
    }
    public PossibleStates getOffState()
    {
        return offState;
    }
    public PossibleStates getMuteState()
    {
        return muteState;
    }
}
//Client
public class StatePatternAlternativeImplementation {
    public static void main(String[] args) {
        System.out.println("***State Pattern Alternative Implementation Demo*** ");
        //Initially TV is Off.
        TV tv = new TV();
        System.out.println("User is pressing buttons in the following sequence:");
        System.out.println("Off->Mute->On->On->Mute->Mute->Off ");
        //TV is already in Off state.Again Off button is pressed.
        tv.pressOffButton();
        //TV is already in Off state.Again Mute button is pressed.
        tv.pressMuteButton();
        //Making the TV on
        tv.pressOnButton();
        //TV is already in On state.Again On button is pressed.
        tv.pressOnButton();
        //Putting the TV in Mute mode
        tv.pressMuteButton();
        //TV is already in Mute mode.Again Mute button is pressed.
        tv.pressMuteButton();
        //Making the TV off
        tv.pressOffButton();
    }
}

Modified Output

Here is the output from the modified implementation.
 ***State Pattern Alternative Implementation Demo***
User is pressing buttons in the following sequence:
Off->Mute->On->On->Mute->Mute->Off
 You pressed Off button. TV is already in Off state
 You pressed Mute button.TV is already in Off state, so Mute operation will not work.
 You pressed On button. Going from Off to On state
    **TV is switched on now.**
You pressed On button. TV is already in On state.
You pressed Mute button.Going from On to Mute mode.
    **TV is silent(mute) now**
 You pressed Mute button.TV is already in Mute mode.
You pressed Off button. Going from Mute mode to Off state.
    **TV is switched off now.**
  1. 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.

     
  2. 9.

    What are the pros and cons of a state design pattern?

    Pros
    • You 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).

    Cons
    • The 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.

     
  3. 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.

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

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