“This is the second day of my participation in the First Challenge 2022.

preface

In the development of Java back-end projects, it is common to encounter scenarios that require state transitions, such as the state of an order, the state of a task, and so on. Enumerations are often used to define states and change their enumerated values as needed.

But in some more complex scenarios, such as state number greater than or equal to 5, or large state owned sub-state scenarios (such as the status of the order [trading] contains momentum cargo has shipped 】 【 】 DengZi state), then directly by changing the way of enumeration values are very difficult to maintain, and can lead to a state change of the code in the project spread, not only on the implementation is not elegant, And when it comes to adding or modifying states. Therefore, we need a technical means to unify the management of state and bundle the code entry for changing state.

That’s what the Finite State Machine (FSM) is for!

This is a series of articles, starting with the basic concepts and their simple implementation

What is a finite state machine

Refer to the description in Reference 1

Finite State Machine is a model of computation based on a hypothetical machine made of one or more states. Only one single state of this machine can be active at the same time. It means the machine has to transition from one state to another in to perform different actions.

A state machine is a mathematical model with multiple states that can transform between states and trigger actions.

A state machine generally contains the following elements

  1. StateThe current state
  2. EventTriggering event
  3. TransitionState change, or the next state.
  4. ActionThe action to be performed

In some articles, names of triggered events and executed actions are interchanged, and here I use the open source definition of State machine Squirrel

That is, in one state, some event is triggered to change to another state, and some action is performed

How to implement a simple state machine

Based on several reference documents, the following four implementation methods are summarized and briefly described. The specific implementation code can be seen in Reference document 2

1. switch... case

  • Save current state
  • The change event is passed when the status changesif... elseDetermine which event, and then the current eventeventforswitch... caseDetermine the action to perform
  • Go to the next state and executeaction

The simple code is as follows

public class StateMachine {
    // Current status
    private State currState;
    
    public StateMachine(State initState) {
        this.currState = initState;
    }
    
    public void transist(Event event) {
        if(The current status is1) {
            switch (event) {
                caseThe event1:
                    // Set the next state
                    / / perform the action
                caseThe event2:... }}/ /...}}Copy the code

This method is the simplest implementation method, when the number of states is small, it is a very efficient implementation scheme. However, in more complex states, this results in a large number of nested if… The else and switch… Case statements, which are laborious to maintain and not elegant to implement.

2. State mode (State Design Pattern)

The original definition is as follows

Allow an object to alter its behavior when its internal state changes.The object will appear to change its class.

This is explained using the translation of Reference 4

Don’t external calls know how to implement state and behavior changes internally

State patterns encapsulate transformation rules, encapsulate state and expose behavior. A change in state looks like a change in behavior. In summary, state patterns do several things

  • State abstract classes need to be definedAbstractState, which needs to include contextContext, and all abstract events (eventObject method
    public abstract class AbstractState {
        // Context information
        protected Context context;
    
        public void setContext(Context context) {
            this.context = context;
        }
        
        // Event operations are placed within specific state definitions
        abstract void event1(a);
        abstract void event2(a);
        / /...
    }
    Copy the code
  • Concrete state needs to inherit and implement abstract state
    public class State1 extends AbstractState {
        @Override
        void event1(a) {
            // set next state
            // do something else
        }
        / /...
    }
    Copy the code
  • contextContextYou need to contain all the information you need, including all state instances, and specify a property indicating which state instance it is currently
    public class Context {
    
        // Current state instance
        protected AbstractState currState;
        protected static final State1 state1 = new State1();
        protected static final State2 state2 = new State2();
        / /...
    
        public Context(AbstractState state) {
            setState(state);
        }
    
        // A specific event calls the current state for execution
        public void event1(a){
            this.currState.event1();
        }
    
        / /...
    }
    Copy the code

As you can see above, state patterns bind to behavior and state, avoiding the need to write a lot of if… The else and switch… Case can be written to easily add new states and change the behavior of an object by simply changing its state.

3. Java enumeration implementation

The main idea is that the State and Event can be defined as an enumeration type, and the use of enumeration can also define the properties of abstract methods, so that the definition and action can be written together

In a nutshell, it is defined as follows

  • Defining state enumeration
    public enum StateEnum {
        State1 {
            @Override
            public void doEvent1(StateMachine stateMachine) {
                // set next state
                // do something else
            }
    
            @Override
            public void doEvent2(StateMachine stateMachine) {
                // set next state
                // do something else
            }
            / /...
        },
        / /...
        ;
    
        public abstract void doEvent1(StateMachine stateMachine);
        public abstract void doEvent2(StateMachine stateMachine);
        / /...
    }
    Copy the code
  • Define event enumeration
    public enum EventEnum {
        Event1 {
            @Override
            public void trigger(StateMachine stateMachine, StateEnum state) {
                returnstate.doEvent1(stateMachine); }},/ /...
        ;
    
        public abstract void trigger(StateMachine stateMachine, StateEnum state);
    }
    Copy the code
  • Assembly state machine
    public class StateMachine {
    
        private StateEnum state;
    
        public StateMachine(StateEnum state) {
            this.state = state;
        }
    
        public void execute(EventEnum event) {
            event.trigger(this.this.state);
        }
    Copy the code

This implementation is abstracted from Reference 2, which you can see for examples

Using enumerations to define makes our code clearer

4. Save the transformation map

In a word, it is to save several elements required for each state transformation of the state machine. When the corresponding event is triggered, all state mapping records are traversed, corresponding records are found according to the state and context information, and the secondary states are obtained through the transformation

The state machine transition record is defined as follows

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class StateMachineRecord {
    // Current status
    private State currentState;
    // Trigger the event
    private Event event;
    / / state
    private State nextState;
    // Perform the action
    private Action action;
}
Copy the code

This approach is essentially the basis for many open source state machines, but in its implementation, custom annotations are used to make code more concise and elegant, and other optimizations are made to improve performance, such as logging indexes and lazy loading.

The latter

This article is a series, and links to subsequent articles will be placed here

Reference documentation

  1. What is a Finite State Machine? In this article, we are going to see… | by Matyas Lancelot Bors | Medium
  2. Comparison of four implementations of Java finite state machines
  3. State Pattern of design Pattern
  4. Java Finite state machine (Design pattern — state pattern)_xiaxveliang-CSDN blog _Java finite state machine
  5. Implementing Simple State Machines with Java Enums | Baeldung