Implementing Simple State Machines with Java Enums – 用Java枚举实现简单状态机

最后修改: 2019年 1月 20日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll have a look at State Machines and how they can be implemented in Java using Enums.

在本教程中,我们将了解状态机以及如何使用Enums在Java中实现它们。

We’ll also explain the advantages of this implementation compared to using an interface and a concrete class for each state.

我们还将解释与使用接口和每个状态的具体类相比,这种实现的优势。

2. Java Enums

2.Java 枚举

A Java Enum is a special type of class that defines a list of constants. This allows for type-safe implementation and more readable code.

Java Enum是一种特殊类型的类,它定义了一个常量的列表。这允许类型安全的实现和更可读的代码

As an example, let’s suppose we have an HR software system that can approve leave requests submitted by employees. This request is reviewed by the Team Leader, who escalates it to the Department Manager. The Department Manager is the person responsible for approving the request.

举个例子,假设我们有一个人力资源软件系统,可以批准员工提交的休假申请。这个请求由小组长审核,小组长将其上报给部门经理。部门经理是负责批准该请求的人。

The simplest enum that holds the states of a leave request is:

保持休假请求状态的最简单的枚举是。

public enum LeaveRequestState {
    Submitted,
    Escalated,
    Approved
}

We can refer to the constants of this enum:

我们可以参考这个枚举的常量。

LeaveRequestState state = LeaveRequestState.Submitted;

Enums can also contain methods. We can write an abstract method in an enum, which will force every enum instance to implement this method. This is very important for the implementation of state machines, as we’ll see below.

枚举也可以包含方法。我们可以在枚举中写一个抽象的方法,这将迫使每个枚举实例实现这个方法。这对于状态机的实现非常重要,我们将在下面看到。

Since Java enums implicitly extend the class java.lang.Enum, they can’t extend another class. However, they can implement an interface, just like any other class.

由于Java枚举隐含地扩展了java.lang.Enum类,它们不能扩展另一个类。然而,它们可以实现一个接口,就像其他的类一样。

Here’s an example of an enum containing an abstract method:

下面是一个包含抽象方法的枚举的例子。

public enum LeaveRequestState {
    Submitted {
        @Override
        public String responsiblePerson() {
            return "Employee";
        }
    },
    Escalated {
        @Override
        public String responsiblePerson() {
            return "Team Leader";
        }
    },
    Approved {
        @Override
        public String responsiblePerson() {
            return "Department Manager";
        }
    };

    public abstract String responsiblePerson();
}

Note the usage of the semicolon at the end of the last enum constant. The semicolon is required when we have one or more methods following the constants.

注意在最后一个枚举常量的末尾使用了分号。当我们在常量后面有一个或多个方法时,分号是必须的。

In this case, we extended the first example with a responsiblePerson() method. This tells us the person responsible for performing each action. So, if we try to check the person responsible for the Escalated state, it will give us “Team Leader”:

在这种情况下,我们用一个responsiblePerson()方法来扩展第一个例子。这告诉我们负责执行每个动作的人。因此,如果我们试图检查负责Escalated状态的人,它将给我们 “Team Leader”。

LeaveRequestState state = LeaveRequestState.Escalated;
assertEquals("Team Leader", state.responsiblePerson());

In the same way, if we check who is responsible for approving the request, it will give us “Department Manager”:

同样,如果我们检查谁负责批准该请求,它将给我们提供 “部门经理”。

LeaveRequestState state = LeaveRequestState.Approved;
assertEquals("Department Manager", state.responsiblePerson());

3. State Machines

3.状态机

A state machine — also called a finite state machine or finite automaton — is a computational model used to build an abstract machine. These machines can only be in one state at a given time. Each state is a status of the system that changes to another state. These state changes are called transitions.

状态机–也叫有限状态机或有限自动机–是一种用于构建抽象机器的计算模型。这些机器在给定的时间内只能处于一种状态。每个状态都是系统的一种状态,会改变成另一种状态。这些状态的变化被称为过渡。

It can get complicated in mathematics with diagrams and notations, but things are a lot easier for us programmers.

在数学中,图表和符号可能会变得很复杂,但对我们程序员来说,事情就容易多了。

The State Pattern is one of the well-known twenty-three design patterns of the GoF. This pattern borrows the concept from the model in mathematics. It allows an object to encapsulate different behaviors for the same object, based on its state. We can program the transition between states and later define separate states.

状态模式是著名的GoF的23种设计模式之一。该模式借用了数学中模型的概念。它允许一个对象根据其状态,为同一个对象封装不同的行为。我们可以对状态之间的转换进行编程,之后再定义独立的状态。

To explain the concept better, we’ll expand our leave request example to implement a state machine.

为了更好地解释这个概念,我们将扩展我们的离开请求例子,以实现一个状态机。

4. Enums as State Machines

4.作为状态机的枚举

We’ll focus on the enum implementation of state machines in Java. Other implementations are possible, and we’ll compare them in the next section.

我们将重点讨论Java中状态机的枚举实现。其他的实现方式也是可能的,我们将在下一节对它们进行比较。

The main point of state machine implementation using an enum is that we don’t have to deal with explicitly setting the states. Instead, we can just provide the logic on how to transition from one state to the next one. Let’s dive right in:

使用枚举实现状态机的要点是,我们不必处理明确设置状态的问题。相反,我们只需提供如何从一个状态过渡到下一个状态的逻辑。让我们直接开始吧。

public enum LeaveRequestState {

    Submitted {
        @Override
        public LeaveRequestState nextState() {
            return Escalated;
        }

        @Override
        public String responsiblePerson() {
            return "Employee";
        }
    },
    Escalated {
        @Override
        public LeaveRequestState nextState() {
            return Approved;
        }

        @Override
        public String responsiblePerson() {
            return "Team Leader";
        }
    },
    Approved {
        @Override
        public LeaveRequestState nextState() {
            return this;
        }

        @Override
        public String responsiblePerson() {
            return "Department Manager";
        }
    };

    public abstract LeaveRequestState nextState(); 
    public abstract String responsiblePerson();
}

In this example, the state machine transitions are implemented using the enum’s abstract methods. More precisely, using the nextState() on each enum constant, we specify the transition to the next state. If needed, we can also implement a previousState() method.

在这个例子中,状态机的转换是用枚举的抽象方法实现的。更确切地说,使用每个枚举常量上的nextState(),我们指定了到下一个状态的过渡。如果需要,我们还可以实现一个previousState()方法。

Below is a test to check our implementation:

下面是一个测试,以检查我们的实现。

LeaveRequestState state = LeaveRequestState.Submitted;

state = state.nextState();
assertEquals(LeaveRequestState.Escalated, state);

state = state.nextState();
assertEquals(LeaveRequestState.Approved, state);

state = state.nextState();
assertEquals(LeaveRequestState.Approved, state);

We start the leave request in the Submitted initial state. We then verify the state transitions by using the nextState() method we implemented above.

我们在Submitted初始状态下开始离开请求。然后我们通过使用上面实现的 nextState()方法来验证状态的转换。

Note that since Approved is the final state, no other transition can happen.

请注意,由于Approved是最终状态,所以不可能发生其他过渡

5. Advantages of Implementing State Machines With Java Enums

5.用Java枚举实现状态机的优势

The implementation of state machines with interfaces and implementation classes can be a significant amount of code to develop and maintain.

用接口和实现类来实现状态机的做法,在开发和维护时可能会有大量的代码。

Since a Java enum is, in its simplest form, a list of constants, we can use an enum to define our states. And since an enum can also contain behavior, we can use methods to provide the transition implementation between states.

由于Java枚举在其最简单的形式上是一个常量列表,我们可以使用枚举来定义我们的状态。由于枚举也可以包含行为,我们可以使用方法来提供状态间的转换实现。

Having all the logic in a simple enum allows for a clean and straightforward solution.

将所有的逻辑放在一个简单的枚举中,可以得到一个干净而直接的解决方案。

6. Conclusion

6.结论

In this article, we looked at state machines and how they can be implemented in Java using Enums. We gave an example and tested it.

在这篇文章中,我们研究了状态机以及如何使用Enums在Java中实现它们。我们举了一个例子并进行了测试。

Eventually, we also discussed the advantages of using enums to implement state machines. As an alternative to the interface and implementation solution, enums provide a cleaner and easier-to-understand implementation of state machines.

最终,我们还讨论了使用枚举来实现状态机的优势。作为接口和实现方案的替代方案,枚举为状态机的实现提供了一种更简洁、更容易理解的方式。

As always, all of the code snippets mentioned in this article can be found in on our GitHub repository.

一如既往,本文中提到的所有代码片段都可以在我们的GitHub仓库中找到。