跳到主要内容

状态类型的定义

在系统中我们经常需要定义一些状态字段,如单据的执行状态、审核状态等状态字段,或者一些类型字段,如用户类型、客户类型等。那如何定义这些状态或类型呢?一般有两种方式即枚举和字典表,那什么时候用枚举,什么时候用字典表呢?

本文给出一个原则:如果这个状态或者类型有不同业务逻辑处理(与特定的代码逻辑绑定),或者在程序中需要判断状态或类型以此来执行不同的代码,则使用枚举来定义。否则使用字典表来定义。

注:通常情况下建议状态一律用枚举,而类型根据情况选择枚举或字典表。用字典表定义的类型通常是经常放生变化,并且与业务代码不相关的。

使用接口约束枚举格式

如果不对枚举的格式进行约束,那可能 5 个人能定义出 10 中格式。在整个系统中就会显得非常混乱。因此我们需要定义一个接口,以此来约束枚举中的字段。

一般情况,在枚举中需要至少两个字段,即值和名称。即这个状态或类型的值,一般为数字,这个值是存到数据库中的;另一个字段是名称,这个名称是要返回到前端展示的。如下面这个审批状态的这个例子

1-待审批;2-审批中;3-审批通过;4-审批驳回;5-撤回;

使用接口的目的是来约束枚举的格式,确保每个人定义的枚举中的值和名称字段的名称是一致的。接口定义如下:

public interface EnumState {

/**
* 获取状态值
*
* @return 状态值
*/
int getValue();

/**
* 获取状态名称
*
* @return 状态名称
*/
String getName();
}
java

开源代码地址:https://gitee.com/d-blue/darkblue-parent/tree/master/darkblue-common/src/main/java/org/dblue/common/enums

枚举状态定义

以上面的审批状态为例,定义如下

public enum AuthState implements EnumState {

/**
* 待提交审批
*/
UN_SUBMIT(1, "待提交"),

/**
* 审批中,提交审批直接变为审批中
*/
PROCESSING(2, "审批中"),

/**
* 审批通过
*/
APPROVE(3, "已通过"),

/**
* 审批拒绝
*/
REJECT(4, "已驳回"),

/**
* 审批取消
*/
CANCEL(5, "已撤回"),
;

@Getter
private final int value;

@Getter
private final String name;

AuthState(int value, String name) {
this.value = value;
this.name = name;
}

public static String getName(Integer value) {
for (AuthState authState : AuthState.values()) {
if (Objects.equals(value, authState.getValue())) {
return authState.getName();
}
}
return null;
}

public boolean equalsTo(Integer value) {
return Objects.equals(this.value, value);
}
}
java

上面的 getNameequalsTo 方法会在后文中进行解释。请先关注接口的应用。

枚举中的可选方法

对于状态或类型枚举,有两个非常常用的应用场景。

  • 根据数据库中存的值获取对应的名称,主要是做前端展示。
  • 判断记录是否为某个状态,根据记录中的状态字段来判断当前这条数据是否是某个状态。

鉴于这两个常用的场景,给出 getNameequalsTo 这两个方法的代码。可以根据自己的需求来决定是否使用这两个方法。

如果 equalsTo 常用,可以将其定义在接口的中。如下:

public interface EnumState {

......

/**
* 状态判断
* @param value 传入的状态
* @return true-一致;false-不一致
*/
default boolean equalsTo(Integer value) {
return Objects.equals(this.getValue(), value);
}
}
java

将其定义为默认方法,可以不用在后续的枚举中实现。这里要注意,参数一定要定义为 Integer 类型,而不要定义为 int 类型,如果定义为了 int 类型,当某个状态没有初始化时,调用 equalsTo 方法会报空指针异常。

Integer authState = null;
AuthState.PROCESSING.equalsTo(authState); // 如果 `equalsTo`参数为 int 类型,这种情况会报空指针异常
java

而定义为 Integer 类型,则不会出现这种情况。

getName 为静态方法,没有办法定义为接口。需要在需要的时候在枚举中实现。也可定义一个工具类来通过工具类来获取名称。在本文的最后会给出工具类的方案。

getNameequalsTo 的使用如下:

返回状态名称到前端
XxxVo vo = ....;
vo.setAuthStateName(AuthState.getName(vo.getAuthState()));
java
判断状态
Xxx entity = this.XxxMapper.selectById(id);
if (AuthState.PROCESSING.equalsTo(entity.getAuthState)) {
// 业务处理逻辑
}
java

通过工具类获取名称

代码如下:

public class EnumNameUtils {

private static final Map<Class<?>, Map<Integer, String>> VALUES_MAP = new HashMap<>();

private EnumNameUtils() {
}

public static String getName(Class<? extends Enum<?>> enumClass, Integer value) {
Map<Integer, String> valueMap = VALUES_MAP.computeIfAbsent(enumClass, key -> {
Map<Integer, String> map = new HashMap<>();
for (Enum<?> anEnum : enumClass.getEnumConstants()) {
if (anEnum instanceof EnumState enumState) {
map.put(enumState.getValue(), enumState.getName());
}
}
return map;
});
return valueMap.get(value);
}

}
java

这里使用 VALUES_MAP 来缓存枚举的信息,以此来提高程序的执行效率。需要注意的是,在并发环境下可能会有问题,高并发环境请自己优化。