Talking about State Mode

table of Contents

background

What is a state machine?

State mode

Refactor business code using state patterns

Optimized implementation

Applicable scene

Expand

Contrast with strategy mode

A general state machine pattern

to sum up

Reference


background

Recently, a function related to document submission and review has been made. Among them, there is a scenario where the document can be rejected in the failed state, or it can become successful after two levels of approval, but the documents that have passed the first level of approval are not allowed to be rejected again and have been rejected The documents are not allowed to be approved again. After simplifying the business scenario here, the status changes are as follows:

                                                      

 

 

In actual development, the above scenario is actually very common. In the author's business scenario, these states are scattered in several domains, and there are fewer states and actions that need to be processed in each domain, so it can be done under separate if else processing.

But, by extension, if these states are concentrated in one domain, or there are more document states and more behaviors, what will happen if you continue to use if else?

It is conceivable that there must be a lot of if else nesting. One change may affect many states, which completely does not conform to the principle of opening and closing . This will make it extremely difficult to maintain in the later stage, and then turn into a bunch of bloated and bad code.

For this kind of stateful object programming , the traditional solution is to take into account all these possible situations, and then use if-else or switch-case statements to make state judgments, and then deal with different situations. However, it is obvious that this approach has natural drawbacks to complex state judgments. The conditional judgment sentences will be too bloated, poorly readable, not scalable, and difficult to maintain. And when adding a new state, a new if-else statement must be added, which violates the "opening and closing principle" and is not conducive to program expansion.

The above problems can be solved well if the " state mode " is adopted . The solution idea of ​​the state mode is:

When the conditional expression that controls the state transition of an object is too complicated, the relevant "judgment logic" is extracted and represented by different classes. In which situation the system is in, directly use the corresponding state class object for processing, so that The original complex logic judgment is simplified, redundant statements such as if-else and switch-case are eliminated, the code is more hierarchical, and it has good scalability.

What is a state machine?

Before understanding the state mode, let us briefly understand the state machine:

Finite state machine (English: finite-state machine, abbreviation: FSM), also known as finite-state automata, or state machine for short, is a mathematical model that represents a finite number of states and the transitions and actions between these states.

The state machine can be summarized into 4 elements, namely, current state, condition, action, and second state. "Existing state" and "condition" are causes, and "action" and "second state" are effects:

  • Current state: refers to the current state.
  • Condition: Also called "event". When a condition is met, an action will be triggered, or a state transition will be performed.
  • Action: The action executed after the condition is met. After the action is executed, it can be migrated to a new state, or it can remain in the original state. The action is not necessary. When the condition is met, it can also directly migrate to the new state without performing any action.
  • Second state: the new state to be moved to after the conditions are met. The "second state" is relative to the "present state". Once activated, the "second state" transforms into a new "present state".

State mode

State mode is an object behavior mode, which has the following advantages.

  • The structure is clear, and the state mode localizes the behaviors related to a specific state into one state, and separates the behaviors of different states to meet the " single responsibility principle ".
  • Display state transitions to reduce interdependence between objects. Introducing different states into independent objects will make state transitions more explicit and reduce the interdependence between objects.
  • The status class has clear responsibilities, which is conducive to program expansion. It is easy to add new states and transitions by defining new subclasses.

Basic state pattern class diagram

 

                                                                            

 

 

In State Pattern, the behavior of a class is changed based on its state. This type of design pattern is a behavioral pattern.

The above is the class diagram of the basic state machine pattern. An abstract state class has multiple concrete state implementations. A context class (Context) holds a concrete state. The handle() method of the concrete state is transferred in the context. The client The end generally only interacts with the Context.

In this way, when the state increases, only the implementation class needs to be added, which conforms to the principle of being open to extension and closed to modification.

Refactor business code using state patterns

Here is the code directly.

Abstract state : The default implementation is defined for each behavior, so that in the specific implementation class, you only need to pay attention to the behaviors supported by the current state.

@Slf4j
public abstract class AbstractState {
    /**
     * 审核通过
     * @return
     */
    public String pass() {
        log.info("非法操作,当前状态不允许该操作");
        return "非法操作,当前状态不允许该操作";
    }
    /**
     * 自动校验
     * @return
     */
    public String autoCheck() {
        log.info("非法操作,当前状态不允许该操作");
        return "非法操作,当前状态不允许该操作";
    }
    /**
     * 复核通过
     * @return
     */
    public String rePass() {
        log.info("非法操作,当前状态不允许该操作");
        return "非法操作,当前状态不允许该操作";
    }
    /**
     * 驳回
     * @return
     */
    public String reject() {
        log.info("非法操作,当前状态不允许该操作");
        return "非法操作,当前状态不允许该操作";
    }
    /**
     * 提交
     * @return
     */
    public String submit() {
        log.info("非法操作,当前状态不允许该操作");
        return "非法操作,当前状态不允许该操作";
    }
}

Specific status category : Only the new and submitted status are listed here

@Slf4j
public class NewState extends AbstractState{

    @Override
    public String submit() {
        //do some thing
        log.info("submit success!");
        return "ok";
    }
}

@Slf4j
public class SubmitState extends AbstractState{
    @Override
    public String reject() {
        //do some thing...
        log.info("reject success!");
        return "ok";
    }

    @Override
    public String autoCheck() {
        //do some thing...
        log.info("autoCheck success!");
        return "ok";
    }
}

......

Context :

The context holds all the state implementation, the client only needs to interact with the context, and then the context transfers the specific implementation class methods.

public class BillStateContext {

    private AbstractState state;

    public BillStateContext(AbstractState state) {
        this.state = state;
    }
    public String pass() {
        return this.state.pass();
    }
    public String rePass(){
        return this.state.rePass();
    }
    public String reject() {
        return this.state.reject();
    }
    public String autoCheck(){
        return this.state.autoCheck();
    }
    public String submit(){
        return this.state.submit();
    }

}

Call the client :

public class Main {

    @Test
    public void  newStateTest(){
        BillStateContext context = new BillStateContext(new NewState());
        Assert.assertEquals("ok", context.submit());
        Assert.assertEquals("非法操作,当前状态不允许该操作", context.reject());
    }

}

The implementation of the basic state pattern is very simple, but the basic state pattern actually has some problems:

  • Implementation class expansion, there will be as many implementation classes as there are states
  • Every time the client calls, a new implementation object of state is required

Optimized implementation

Here is an optimized version, the core idea is to use enumeration instead of multiple implementation classes:

Here replace the original abstract class with the state interface:

In jdk8, the interface already allows the method to have a default implementation, you only need to add the keyword default , and the default implementation method is not mandatory to implement, but the implementation class can still choose the Override default implementation.

public interface BillStateInterface {
    /**
     * 审核通过
     * @return
     */
    default String pass() {
        return "非法操作,当前状态不允许该操作";
    }
    /**
     * 自动校验
     * @return
     */
    default String autoCheck() {
        return "非法操作,当前状态不允许该操作";
    }
    /**
     * 复核通过
     * @return
     */
    default String rePass() {
        return "非法操作,当前状态不允许该操作";
    }
    /**
     * 驳回
     * @return
     */
    default String reject() {
        return "非法操作,当前状态不允许该操作";
    }
    /**
     * 提交
     * @return
     */
    default String submit() {
        return "非法操作,当前状态不允许该操作";
    }
}

Enumeration implementation of state:

public enum BillStates implements BillStateInterface{

    NEW{
        @Override
        public String submit() {
            return "ok";
        }
    },
    SUBMIT{
        @Override
        public String reject() {
            return "ok";
        },
        @Override
        public String autoCheck() {
            return null;
        }
    },
    WAIT{
        @Override
        public String rePass() {
            return "ok";
        }
    }
......
}

Client call entry:

public class Main {
    @Test
    public void  newStateTest(){
        BillStateContext context = new BillStateContext(BillStates.NEW);
        Assert.assertEquals("ok", context.submit());
        Assert.assertEquals("非法操作,当前状态不允许该操作", context.reject());
    }
}

The optimized implementation aggregates all the states into the enumeration, which is more convenient to modify, and will not cause the implementation class to bloat, but you need to be careful not to gather all the business code in one class, causing the implementation class to be too large.

Digression: In "Refactoring and Improving the Design of Existing Code", there is a question about Large Class :

If you want to use a single class to do too many things, there will often be too many instance variables in it. Once so, Duplicated Code will follow. You can use Extract Class to extract several variables together into a new class. When refining, you should select variables that are related to each other in the class and put them together. For example, "depositAmount" and "depositCurrency" may belong to the same class. Usually if several variables in a class have the same prefix or suffix, it means that there is an opportunity to refine them into a certain component. If this component is suitable as a subclass, you will find that Extract Subclass is often simpler. Sometimes class does not use all instance variables at all times. If this is the case, you may be able to use Extract Class or Extract Subclass multiple times. Like "too many instance variables", if there is too much code in a class, it is also an excellent breeding place for "code duplication, confusion, and death". The simplest solution (remember, we like simple solutions) is to eliminate redundant things inside the class. If there are five "hundred-line functions" and many of them have the same code, then perhaps you can turn them into five "ten-line functions" and ten refined "two-line functions".

Applicable scene

Generally, state mode can be considered in the following situations.

  • When an object's behavior depends on its state, and it must change its behavior according to the state at runtime, you can consider using the state pattern.
  • An operation contains a huge branch structure, and these branches are determined by the state of the object.

Combine Flyweight Mode Extension

In some cases, there may be multiple environment objects that need to share a set of states. At this time, it is necessary to introduce the flyweight mode, and place these specific state objects in the collection for program sharing. The structure diagram is shown in the figure:

                                                                

 

 

Contrast with strategy mode

The strategy mode is also one of the better solutions to eliminate if else, and the UML class diagram architecture of the state mode and the strategy mode is almost the same, but the application scenarios of the two are different. The multiple algorithm behaviors of the strategy mode can be satisfied by choosing one of them. They are independent of each other. Users can change the strategy algorithm by themselves. There is a relationship between the states of the state mode, and there is an automatic switching state between each other under certain conditions. And the user cannot specify the status, only the initial status can be set.

A general state machine pattern

I saw on the Internet that some students have designed a very comprehensive state machine, including the pre- and post-operation of state escaping, event notification, state monitoring, state synchronization, etc. The design is very comprehensive, but it is also "heavier". Business needs can be referred to as a trade-off, the class diagram is as follows:

 

to sum up

Of course, design patterns should not be mechanically applied, and avoid over-designing and complicate simple problems. The design pattern actually provides us with a set of methodology that can be used for reference when we encounter complex business scenarios. We must combine the actual business and use it flexibly.

Doing business technology is easy to say, but it is difficult to do well. How to abstract a suitable model from the chaotic business scene? How to flexibly respond to rapidly changing needs? How to train structured thinking and see through the nature of demand at a glance? These all require accumulation and continuous learning.

One side is the underlying technology, and the other is the complex business, with different directions, but the same approach is required, and the same methodology and design philosophy are required. To do business technology, you must grasp both hands, understand, verify, and continuously upgrade the methodology you have learned in business practice to grow.

Long-term study, continuous growth, and encourage everyone!

Reference

Guess you like

Origin blog.csdn.net/vipshop_fin_dev/article/details/109900047