The basic concept

define

State is a behavioral design pattern that lets an object alter its behavior when its internal state changes. It appears as if the object changed its class.

Allowing an object to change its behavior when its internal state changes, the object appears to have changed its class.

FSM

The concept of state mode is similar to FSM (finite state machine). FSM represents a mathematical model of a finite number of states and behaviors such as transitions and actions between these states. At any given moment, a program can be in a finite number of states. In any unique state, the program behaves differently and can be switched from one state to another. Of course, depending on the current state, the program may or may not switch to some other state. These switching rules, called transitions, are also finite and predetermined.

For example

The function of the iPhone’s home button depends on the current state of the device:

  • Off: No response.
  • First startup after startup: Unlock with password.
  • Non-first boot: Unlock with a password or fingerprint.
  • After startup: Return to the main page.

The class diagram

The state pattern suggests creating new classes for all possible states of objects and extracting into these classes any behavior that exists only in a particular state. Rather than an object implementing all the behavior individually, the context stores a reference to the current state object and delegates all state-related work to that object.

At its core is encapsulation, where a change in state causes a change in behavior that looks from the outside as if the object’s corresponding class has changed. The class diagram for the state pattern is shown below.

Three roles in state mode.

  • State — Abstract State roles

    Interface or abstract class that is responsible for object state definition and encapsulates environment roles to enable state switching.

  • ConcreteState – ConcreteState roles

    Each specific state must fulfill two responsibilities: the management of the behavior of the state and the handling of the trending state, which is, in plain English, what to do in the state and how to transition from the state to other states.

  • Context — Environment role

    Define the interface required by the client and be responsible for switching the specific state.

This structure looks similar to the “strategy” pattern, but with one key difference. In the state pattern, specific states may know each other and begin to transition from one state to another, while policies almost never know each other.

Code example – Elevator

V 1.0

We use the program to achieve a simple elevator class, the elevator has the following action: open, close, run, stop. Let’s start with class diagram design:

Elevator interface:

public interface ILift {
     public void open();
     public void close();
     public void run();
     public void stop();
}
Copy the code

Elevator implementation class:

public class Lift implements ILift {
     public void close() {
        System.out.println("Elevator door closed...");
     }
     public void open() {
        System.out.println("Elevator door open...");
     }
     public void run() {
        System.out.println("The elevator is running...");
     }
     public void stop() {
        System.out.println("The elevator has stopped..."); }}Copy the code

Scene class call

public class Client { public static void main(String[] args) { ILift lift = new Lift(); lift.open(); lift.close(); lift.run(); lift.stop(); }}Copy the code

V 2.0

Add preconditions for the execution of the four actions of the elevator, specifically that is, to do a specific thing in a specific state.

  • Open the door status

    The only action the elevator can do is to close the door.

  • Closed state

    The actions that can be performed are: open the door (I don’t want to take the elevator), stop (I forgot to press the floor number), and run.

  • Running state

    All the elevator can do is stop.

  • Stop state

    The elevator has two optional actions: continue running and open the door.

Four constants are defined in the interface, which respectively represent the four states of the elevator: open door state, closed door state, running state and stop state. Then, in the implementation class, each action of the elevator should be judged on the state to determine whether it can be executed.

Elevator implementation class:

public class Lift implements ILift {
     private int state;
     public void setState(int state) { this.state = state; } // Lift door closed public voidclose() {// In what state can the elevator be closed switch(this.state) {case OPENING_STATE:
                this.closeWithoutLogic();  
                this.setState(CLOSING_STATE);
                break;
            case CLOSING_STATE:  
                break;
            case RUNNING_STATE: 
                break;
            case STOPPING_STATE:  
                break; }} // Lift door open public voidopen() {// What state can the elevator start? Switch (this.state){... . }} // Lift starts running public voidrun() { switch(this.state){ ... . }} // Lift stops public voidstop() { switch(this.state){ ... . }}}Copy the code

Once we start adding more and more states and state-related behaviors, the weakness of conditions-based judgments becomes apparent. Most methods will contain poor conditional judgments that choose the correct behavior of the method based on the current state. Such code is difficult to maintain because any changes to the transformation logic may require changing state conditions in each method.

As the project progresses, this problem will become bigger and bigger. It is difficult to predict all possible states and transitions during the design phase. As a result, this class can become bloated over time.

V 3.0

How do you solve this problem with state patterns?

public abstract class LiftState{
     protected Context context;
     public void setContext(Context _context){
        this.context = _context;
     }
     public abstract void open();
     public abstract void close();
     public abstract void run();
     public abstract void stop();
}
Copy the code

Open door state category:

Public class OpenningState extends LiftState {public class OpenningState extends LiftState {public class OpenningState extends LiftStateclose() {/ / state modify super. Context. SetLiftState (context. CloseingState); / / actions entrusted CloseState to perform super. Context. GetLiftState (). The close (); } // open the elevator dooropen() {
        System.out.println("Elevator door open..."); } // The elevator runs when the door is open. @Override public voidrun() {
        //donothing; } // Do you still open the door? public voidstop() {
        //donothing; }}Copy the code

Closed state:

Public class ClosingState extends LiftState {public class ClosingState extends LiftState {public class ClosingState extends LiftStateclose() {
        System.out.println("Elevator door closed..."); } // public void Override public voidopen() { super.context.setLiftState(Context.openningState); State super / / set to open door. Context. GetLiftState (). The open (); } // The elevator runs when the door is closedrun() { super.context.setLiftState(Context.runningState); / / set to run state super. Context. GetLiftState (). The run (); } // If the door is closed, I will not press the floor @override public voidstop() { super.context.setLiftState(Context.stoppingState); / / set to stop state super. Context. GetLiftState (), stop (); }}Copy the code

Context class

Public final static OpenningState OpenningState = new OpenningState(); public final static ClosingState closeingState = new ClosingState(); public final static RunningState runningState = new RunningState(); public final static StoppingState stoppingState = new StoppingState(); Private LiftState LiftState; public LiftStategetLiftState() {
        return liftState;
     }
     public void setLiftState(LiftState liftState) { this.liftState = liftState; / / the current environment of notification to all this. In the implementation class liftState. SetContext (this); } public voidopen(){
        this.liftState.open();
     }
     public void close(){
        this.liftState.close();
     }
     public void run(){
        this.liftState.run();
     }
     public void stop(){ this.liftState.stop(); }}Copy the code

Caller:

public class Client { public static void main(String[] args) { Context context = new Context(); context.setLiftState(new ClosingState()); context.open(); context.close(); context.run(); context.stop(); }}Copy the code

conclusion

Advantages of state patterns

  1. Conform to the single responsibility principle: Organize code related to a particular state into a separate class.

  2. Conforming to the open closed principle: Introduce new state without changing existing state classes or contexts.

  3. Long if or switch judgments are avoided.

Disadvantages of state mode

  1. If you have a lot of states, you have too many subclasses, class inflation. It is not uncommon for a thing to have many states, and using state mode exclusively would have too many subclasses to manage.
  2. Applying state patterns can be overdesigned if the class has only a few states or changes very little.

Reference:

  1. Zen of design patterns
  2. State