On design mode state mode

On design mode state mode (13)

preface

In fact, the state mode is also a very common mode. The most common application scenario is thread state switching. The most commonly used way is to decouple If/else. In addition, this mode can be combined with the "responsibility chain mode" to match various state switching effects, and a simple "workflow" can be simulated with the design mode.

Advantages and disadvantages:

The state mode is obviously used to decouple a large number of if / else modes, so its advantage is very prominent, that is, it can simplify a large number of if / else judgments, but the disadvantage page is very obvious, that is, the execution of the program is limited by the state. If there are many state constants, there will still be a large number of if / else phenomena, The state mode is the same as the policy mode. Once the situation is very complex, it is easy to cause class expansion. Of course, in most cases, this disadvantage can be almost ignored and is always too much.

The state pattern can be reflected in lambada expressions after jdk1.8. lambada implements the parameter "methodization" of java, which greatly simplifies the expansion of classes, but it may be difficult to understand. Once it is complex, it is still recommended to use state to realize switching, which is more convenient for maintenance.

Structure diagram of status mode:

The following is the structure diagram of the state mode, which is relatively simple and seems to have nothing special. However, it seems easy to be confused when we compare it with the "policy mode". Let's take a look at the comparison between the two structure diagrams:

State mode structure diagram:

State mode structure diagram

Structure diagram of policy mode:

Through comparison, we can see that the two modes of state and policy are very similar, so how should we distinguish the two modes? "In normal development work, if an object has many states and its behavior is different in different states, we can use the state mode to solve this problem, but if you let the same thing have different behaviors at different times, you can use the policy mode to trigger different behaviors.". For example, if you want the switch to have different behaviors, you need to design two state switches, and then distribute the logic to different states to complete the trigger during event processing. If you want to realize the "mode" of discount or preferential promotion, full reduction and so on, it is more suitable to use the strategy. Of course, it doesn't matter if you can't distinguish, Which one is more skilled can be used.

Case: candy machine

This is the candy machine in the case of state mode in head first design mode. We can see from the figure below that if you use simple code to complete the following work, there will be many embarrassing situations, such as a large number of complex if/else codes. Let's see how to design this candy machine in an ordinary way?

Do not use design mode

Without using design patterns, we usually define state constants, such as setting enumerations or directly setting the flag bit of final

  1. First, we need to divide objects, candy machine and candy. Candy machine includes the total amount of coins, the amount of candy, etc.
    1. Four statuses are defined: sold out, being sold, coins present and no coins
  2. In order to realize the state, we need to design constants similar to enumeration to represent the state of candy machine.
    1. The state is set to constant, and the candy machine needs the state of the built-in machine
  3. Finally, let the candy machine work internally with logic code and judgment. Of course, there will be a lot of if/else judgment.

Finally, our code is expressed as follows. Using the traditional mode, we are likely to write similar code:

MechanicaState: defines the state of the candy machine. Of course, it can be used as the private internal class definition of the candy machine, or it can be designed as enumeration. Here, laziness is designed as a constant class.

/**
 * Machine status
 */
public final class MechanicaState {

    /**
     * sell out
     */
    public static final int SOLD_OUT = 0;
    /**
     * Coin present
     */
    public static final int HAS = 1;
    /**
     * No coins
     */
    public static final int NOT = 2;

    /**
     * In candy sold
     */
    public static final int SOLD = 4;


}

Candy mechaica: candy maker, which contains the internal working methods of candy. You can see that there are many redundant If/else judgments:

/**
 * Candy machine
 */
public class CandyMechanica {

    /**
     * The default is sold out
     */
    private int sold_state = SOLD_OUT;

    /**
     * Candy quantity
     */
    private int count = 0;

    public CandyMechanica(int count) throws InterruptedException {
        if(count <= 0){
            throw new InterruptedException("initialization failed");
        }else {
            sold_state = NOT;
        }
        System.out.println("Welcome to the candy machine. The current candy quantity of the candy machine is:"+ count);
        this.count = count;
    }


    /**
     * Start the candy machine
     */
    public void startUp(){
        switch (sold_state){
            case NOT:
                System.out.println("There is no coin in the candy machine at present. Please put in the coin first");
                break;
            case SOLD:
                System.out.println("Candy is sold. Please wait");
                break;
            case HAS:
                sold_state = SOLD;
                candySold();
                break;
            case SOLD_OUT:
                System.out.println("The candy has sold out");
                break;


        }
    }

    /**
     * Operation of coin insertion
     */
    public void putCoin(){
        switch (sold_state){
            case NOT:
                sold_state = HAS;
                System.out.println("Coin input succeeded, please turn on the candy machine");
                break;
            case SOLD:
                System.out.println("Candy is sold, please do not put it again");
                break;
            case HAS:
                System.out.println("Coins already exist, please do not put them in again");
                break;
            case SOLD_OUT:
                System.out.println("The candy has sold out,Your coin will be returned later");
                break;


        }
    }


    /**
     * Sell candy
     */
    public void candySold(){
        switch (sold_state){
            case NOT:
                System.out.println("There is no coin in the current machine. Please put in the coin first");
                break;
            case SOLD:
                System.out.println("The candy has been sold. Please take your candy");
                count--;
                if(count == 0){
                    System.out.println("Candy has sold out at present");
                    sold_state = SOLD_OUT;
                }
                sold_state = NOT;
                break;
            case HAS:
                sold_state = NOT;
                System.out.println("Coins already exist, please do not put them in again");
                break;

        }
    }



}

Finally, a simple unit test is carried out for the above candy machine:

/**
 * unit testing 
 */
public class Main {

    public static void main(String[] args) throws InterruptedException {

        CandyMechanica candyMechanica = new CandyMechanica(5);
        candyMechanica.putCoin();

        candyMechanica.startUp();
        candyMechanica.startUp();
        candyMechanica.putCoin();
        candyMechanica.putCoin();
        candyMechanica.startUp();
        candyMechanica.putCoin();
        candyMechanica.startUp();
        candyMechanica.putCoin();
        candyMechanica.startUp();
        candyMechanica.putCoin();
        candyMechanica.startUp();
    }
}/*Operation results:
Welcome to the candy machine. The current number of candy in the candy machine is: 5
 Coin input succeeded, please turn on the candy machine
 The candy has been sold. Please take your candy
 There is no coin in the candy machine at present. Please put in the coin first
 Coin input succeeded, please turn on the candy machine
 Coins already exist, please do not put them in again
 The candy has been sold. Please take your candy
 Coin input succeeded, please turn on the candy machine
 The candy has been sold. Please take your candy
 Coin input succeeded, please turn on the candy machine
 The candy has been sold. Please take your candy
 Coin input succeeded, please turn on the candy machine
 The candy has been sold. Please take your candy
 Candy has sold out at present

Process finished with exit code 0

*/

Refactoring using state patterns

Then we use the state pattern to reconstruct the above code. We focus on the class CandyMechanica. Its three methods are coupled with a large number of if/else judgments. When writing this code, it will not only make the code very rigid, but also easy to make mistakes. I think no one will like to write the above code, So let's use the state mode to see how to refactor:

  1. The candy machine is divided into four states, but they have similar behavior: pushing coins, starting the machine and pushing candy
    1. The common behavior of timing states in the form of interfaces
  2. We extract the above three states as public methods of States, but where is the context?
  3. The expression of context here is candy machine. We use the form of combining candy machines within the state to realize the state "switching" of candy machine.
  4. Note: because the built-in form of internal classes is used, sometimes many judgments can be simplified. More often, it is recommended to extract them as separate classes.

Finally, his expression is as follows:

Candy state: the candy machine is divided into four states, but they have similar behavior: pushing coins, starting the machine and pushing candy

/**
 * Candy state
 */
public interface CandyState {

    /**
     * Start the candy machine
     */
    void startUp();

    /**
     * Put in a coin
     */
    void putCoin();

    /**
     * Launch candy
     */
    void candySold();
}

The candy machine is still very complex. Of course, it doesn't look like the above form:

Candy mechanica: a rewritten candy machine that decouples all States and extracts them as objects.

/**
 * State mode rewriting candy machine
 */
public class CandyMechanica implements CandyState {

    private int count;
    /**
     * current state
     */
    private CandyState nowState;

    // There are coins
    private CandyState hasState;
    // No coin
    private CandyState notState;
    // sell out
    private CandyState solidOutState;
    // In sale
    private CandyState solidState;


    public CandyMechanica(int count) throws InterruptedException {
        notState = new NotState(this);
        solidOutState = new SoldOutState(this);
        hasState = new HasState(this);
        solidState = new SoldOutState(this);
        if (count <= 0) {
            throw new InterruptedException("initialization failed");
        } else {
            nowState = notState;
        }
        this.count = count;
    }

    @Override
    public void startUp() {
        nowState.startUp();
    }

    @Override
    public void putCoin() {
        nowState.putCoin();
    }

    @Override
    public void candySold() {
        nowState.candySold();
    }


    /**
     *
     */
    public static class HasState implements CandyState {

        private CandyMechanica candyMechanica;

        public HasState(CandyMechanica candyMechanica) {
            this.candyMechanica = candyMechanica;
        }

        @Override
        public void startUp() {
            candyMechanica.nowState = candyMechanica.solidState;
            candyMechanica.candySold();
            System.out.println("Candy is sold. Please wait");
        }

        @Override
        public void putCoin() {
            System.out.println("There are candies now. Please do not put them in again");
        }

        @Override
        public void candySold() {
            System.out.println("The candy has sold out");
        }
    }


    /**
     * Sold out status
     */
    public static class SoldOutState implements CandyState {

        private CandyMechanica candyMechanica;

        public SoldOutState(CandyMechanica candyMechanica) {
            this.candyMechanica = candyMechanica;
        }

        @Override
        public void startUp() {
            System.out.println("The candy has sold out");
        }

        @Override
        public void putCoin() {
            System.out.println("The candy has sold out,Your coin will be returned later");
        }

        @Override
        public void candySold() {
            System.out.println("The candy has sold out");
        }
    }

    /**
     * Sold status
     */
    public static class SoldState implements CandyState {

        private CandyMechanica candyMechanica;

        public SoldState(CandyMechanica candyMechanica) {
            this.candyMechanica = candyMechanica;
        }

        @Override
        public void startUp() {
            System.out.println("Candy is sold. Please wait");
        }

        @Override
        public void putCoin() {
            System.out.println("Candy is sold, please do not put it again");
        }

        @Override
        public void candySold() {
            System.out.println("The candy has been sold. Please take your candy");
            candyMechanica.count--;
            if (candyMechanica.count == 0) {
                System.out.println("Candy has sold out at present");
                candyMechanica.nowState = candyMechanica.solidOutState;
            }
            candyMechanica.nowState = candyMechanica.notState;
        }
    }

    /**
     * No coin status
     */
    public static class NotState implements CandyState {

        private CandyMechanica candyMechanica;

        public NotState(CandyMechanica candyMechanica) {
            this.candyMechanica = candyMechanica;
        }

        @Override
        public void startUp() {
            System.out.println("There is no coin in the candy machine at present. Please put in the coin first");
        }

        @Override
        public void putCoin() {
            candyMechanica.nowState = candyMechanica.hasState;
            System.out.println("Coin input succeeded, please turn on the candy machine");
        }

        @Override
        public void candySold() {
            System.out.println("There is no coin in the current machine. Please put in the coin first");
        }
    }
}

The last part is the unit test:

/**
 * unit testing 
 */
public class Main {

    public static void main(String[] args) throws InterruptedException {
        CandyMechanica candyMechanica = new CandyMechanica(5);
        candyMechanica.putCoin();
        candyMechanica.putCoin();
        candyMechanica.startUp();
        candyMechanica.candySold();
        candyMechanica.startUp();
        candyMechanica.putCoin();
        candyMechanica.startUp();
        candyMechanica.putCoin();
        candyMechanica.startUp();
        candyMechanica.putCoin();
        candyMechanica.startUp();
        candyMechanica.putCoin();
        candyMechanica.startUp();
    }
}/*Operation results:
Welcome to the candy machine. The current number of candy in the candy machine is: 5
 Coin input succeeded, please turn on the candy machine
 Coins already exist, please do not put them in again
 The candy has been sold. Please take your candy
 There is no coin in the current machine. Please put in the coin first
 There is no coin in the candy machine at present. Please put in the coin first
 Coin input succeeded, please turn on the candy machine
 The candy has been sold. Please take your candy
 Coin input succeeded, please turn on the candy machine
 The candy has been sold. Please take your candy
 Coin input succeeded, please turn on the candy machine
 The candy has been sold. Please take your candy
 Coin input succeeded, please turn on the candy machine
 The candy has been sold. Please take your candy
 Candy has sold out at present
*/

summary

There are many codes in this article, and the state mode is the same as the strategy. Just take a look at the sample code.

Let's summarize the characteristics of state mode. The advantages of using state mode are as follows:

  • "Decouple the application code to facilitate reading and maintenance.". We can see that in the first scheme, we use a large number of if/else to make logical judgment and process various states and logic together. It may not be a big problem when we have few application related object states, but once the object states become more, the maintenance of deeply coupled code is a nightmare.
  • "Encapsulating changes into specific state objects is equivalent to localizing and encapsulating changes, which is conducive to future maintenance and expansion.". After using the state mode, we encapsulate the relevant operations into the corresponding state. If we want to modify or add a new state, it is also very convenient. There are few modifications to the code, and the scalability is good.
  • Through composition and delegation, objects can change their behavior by changing their state at run time. We just need to draw the state of the object and focus on the state change of the object and what behavior each state has. This makes our development easier and less error prone, and can ensure that the quality of the code we write is good.

Write at the end

The use frequency of state mode is similar to that of policy mode. It is also a design mode that can quickly simplify the code.

Added by JimmyD on Tue, 02 Nov 2021 21:55:56 +0200