状态类型的定义
在系统中我们经常需 要定义一些状态字段,如单据的执行状态、审核状态等状态字段,或者一些类型字段,如用户类型、客户类型等。那如何定义这些状态或类型呢?一般有两种方式即枚举和字典表,那什么时候用枚举,什么时候用字典表呢?
本文给出一个原则:如果这个状态或者类型有不同业务逻辑处理(与特定的代码逻辑绑定),或者在程序中需要判断状态或类型以此来执行不同的代码,则使用枚举来定义。否则使用字典表来定义。
注:通常情况下建议状态一律用枚举,而类型根据情况选择枚举或字典表。用字典表定义的类型通常是经常放生变化,并且与业务代码不相关的。
使用接口约束枚举格式
如果不对枚举的格式进行约束,那可能 5 个人能定义出 10 中格式。在整个系统中就会显得非常混乱。因此我们需要定义一个接口,以此来约束枚举中的字段。
一般情况,在枚举中需要至少两个字段,即值和名称。即这个状态或类型的值,一般为数字,这个值是存到数据库中的;另一个字段是名称,这个名称是要返回到前端展示的。如下面这个审批状态的这个例子
1-待审批;2-审批中;3-审批通过;4-审批驳回;5-撤回;
使用接口的目的是来约束枚举的格式,确保每个人定义的枚举中的值和名称字段的名称是一致的。接口定义如下:
- 状态枚举定义
- 类型枚举定义
public interface EnumState {
/**
* 获取状态值
*
* @return 状态值
*/
int getValue();
/**
* 获取状态名称
*
* @return 状态名称
*/
String getName();
}
public interface EnumType extends EnumState {
}
枚举状态定义
以上面的审批状态为例,定义如下
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);
}
}
上面的 getName
和 equalsTo
方法会在后文中进行解释。请先关注接口的应用。
枚举中的可选方法
对于状态或类型枚举,有两个非常常用的应用场景。
- 根据数据库中存的值获取对应的名称,主要是做前端展示。
- 判断记录是否为某个状态,根据记录中的状态字段来判断当前这条数据是否是某个状态。
鉴于这两个常用的场景,给出 getName
和 equalsTo
这两个方法的代码。可以根据自己的需求来决定是否使用这两个方法。
如果 equalsTo
常用,可以将其定义在接口的中。如下:
public interface EnumState {
......
/**
* 状态判断
* @param value 传入的状态
* @return true-一致;false-不一致
*/
default boolean equalsTo(Integer value) {
return Objects.equals(this.getValue(), value);
}
}
将其定义为默认方法,可以不用在后续的枚举中实现。这里要注意,参数一定要定义为 Integer
类型,而不要定义为 int
类型,如果定义为了 int
类型,当某个状态没有初始化时,调用 equalsTo
方法会报空指针异常。
Integer authState = null;
AuthState.PROCESSING.equalsTo(authState); // 如果 `equalsTo`参数为 int 类型,这种情况会报空指针异常
而定义为 Integer
类型,则不会出现这种情况。
而 getName
为静态方法,没有办法定义为接口。需要在需要的时候在枚举中实现。也可定义一个工具类来通过工具类来获取名称。在本文的最后会给出工具类的方案。
getName
和 equalsTo
的使用如下:
XxxVo vo = ....;
vo.setAuthStateName(AuthState.getName(vo.getAuthState()));
Xxx entity = this.XxxMapper.selectById(id);
if (AuthState.PROCESSING.equalsTo(entity.getAuthState)) {
// 业务处理逻辑
}
通过工具类获取名称
代码如下:
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);
}
}
这里使用 VALUES_MAP
来缓存枚举的信息,以此来提高程序的执行效率。需要注意的是,在并发环境下可能会有问题,高并发环境请自己优化。