前言
最近发现,当一个项目越做越大的时候,会有很多的Switch Case枚举的情况,当这个枚举不加新的枚举项的时候还好,如果新加了一个,那真的是噩梦,项目中到处散落的Switch Case都要修改,这会导致什么问题?
- 在某处的Switch Case忘记新增的枚举项,编译和启动的时候并不会报错,只有当线上执行到这块代码时才会出问题,然后重新打包上线
- 修改了,但是影响了以前的代码,简单来说,不是对修改关闭
简单的例子
有下面一个枚举:
public enum MiaoAliPayStatusEnum {
// 1=成功,0=队列中,暂未处理,-1=失败
SUCCESS(1, "已打款成功"),
FAIL(-1, "打款失败"),
ING(0, "队列中,暂未处理");
private Integer code;
private String desc;
MiaoAliPayStatusEnum(Integer code, String desc) {
this.code = code;
this.desc = desc;
}
}
我们在代码中需要对这几项分别进行不同的处理,最简单的方法就是SwitchCase,如下:
switch (payStatusEnum){
case FAIL:
System.out.println("打款失败");
break;
case ING:
System.out.println("正在打款中,请稍等");
break;
case SUCCESS:
System.out.println("打款成功...");
break;
default:
// log.warn("no enum item");
throw new RuntimeException("no enum item");
}
现在需要新增一个打款待确认,则需要在switch case中新增逻辑,如果忘了,则会抛出RuntimeException异常或者打印一个warn或error的日志,总的来说,我们还得修改后重新上线。
一些名词
枚举项:即枚举中的每一项,如MiaoAliPayStatusEnum中SUCCESS就是一枚举项。
解决思路
分析一下上面的情况,解决思路如下:
- 如果新增的枚举项没有对应的处理策略,则让项目启动不起来,保证各个枚举项都要被处理。
- 将不同的处理策略聚拢在一起,防止到处修改代码导致项目不可控
模块分析
总体分四块:
- 每个枚举项中的处理策略
- 校验每个枚举项是否都绑定到处理策略的校验模块
- 根据枚举项映射到对应执行策略的匹配模块
- 处理策略注册模块
![](https://img.haomeiwen.com/i11013663/62ba239d836f3f8c.png)
1. 处理策略
即将原Switch Case中每一个枚举项下的具体执行逻辑放到一个单独的类中,其抽象的接口如下:
public interface IEnumProcessor<U,R> {
R process(U u);
}
这里使用了泛型,U传参类型,R返回值类型。
2. 校验模块
校验是否所有的枚举项都绑定了一个处理策略,如果没绑定,则在启动的时候就抛出异常。
接口:
public interface IEnumChecker<T extends Enum<T>> {
/***
* 校验是否所有的枚举项都绑定了处理策略
*/
void registerCheck();
/**
* 要校验的枚举的类型
* @return
*/
Class<T> getEnumType();
}
抽象类:
封装了校验公共逻辑,使用了构造代码块,如果该抽象类的子类被实例化,那么该构造块就会被执行,触发校验逻辑。
其中register()注册方法的内容是变化的,我们将其抽象,下沉到子类中去实现。
public abstract class AbstractEnumChecker<T extends Enum<T>,U,R> implements IEnumChecker<T> {
{
this.registerCheck();
}
@Override
public void registerCheck() {
EnumProcessorRegister<T,U,R> register = register();
validateAllEnumItemMatchProcessor(register);
}
private void validateAllEnumItemMatchProcessor(EnumProcessorRegister<T,U,R> register) {
Class<T> enumType = getEnumType();
T[] enumConstants = enumType.getEnumConstants();
for (T enumConstant : enumConstants) {
boolean hasHandler = register.isHasHandler(enumConstant);
if (!hasHandler){
throw new RuntimeException(this.getClass().getSimpleName() + " don't config all enum processor,"+ enumConstant.name() + " is missing");
}
}
}
protected abstract EnumProcessorRegister<T,U,R> register();
}
3. 注册模块
注册模块就是将枚举项和对应的处理策略放到map中对应起来
public class EnumProcessorRegister<T extends Enum<T>,U,R> {
private Map<T, IEnumProcessor<U,R>> container = new HashMap<>();
/**
* 将枚举t和处理策略processor绑定
*/
public EnumProcessorRegister<T,U,R> register(T t, IEnumProcessor<U,R> processor){
container.put(t,processor);
return this;
}
/**
* 获取枚举对应的处理策略
* @param t
* @return
*/
public IEnumProcessor<U,R> getProcessor(T t){
return container.get(t);
}
/**
* 判断枚举项是否绑定了处理策略
*/
public boolean isHasHandler(T t){
IEnumProcessor<U,R> iEnumProcessor = container.get(t);
if (iEnumProcessor != null){
return true;
}
return false;
}
}
4. 匹配且执行模块
面向业务类,直接调用此模块即可,如下:
public interface IEnumMatcher<T extends Enum<T>,U,R> {
/**
* 根据枚举t 执行对应的处理策略
* @param t 枚举项
* @param u 处理策略参数
* @return 处理策略返回值
*/
R matchAndProcess(T t,U u);
}
抽象类:
抽象类同时继承了校验模块
public abstract class AbstractEnumMatcher<T extends Enum<T>,U,R> extends AbstractEnumChecker<T,U,R> implements IEnumMatcher<T,U,R> {
@Override
public R matchAndProcess(T t, U u) {
// 获取注册信息
EnumProcessorRegister<T,U,R> register = register();
// 获取处理策略
IEnumProcessor<U, R> processor = register.getProcessor(t);
// 执行逻辑
return processor.process(u);
}
}
举个栗子
举个栗子看如何使用:
首先定义处理策略
public class PayStatusEnumHandler implements IEnumProcessor<String,String> {
@Override
public String process(String s) {
return "打款成功";
}
}
这里只返回一个打款成功的字符串。
再次定义个Matcher注册匹配策略,如下:
@Component
public class PayStatusEnumMatcher extends AbstractEnumMatcher<MiaoAliPayStatusEnum,String,String> {
/**
* 返回要处理的枚举类型
*/
@Override
public Class<MiaoAliPayStatusEnum> getEnumType() {
return MiaoAliPayStatusEnum.class;
}
/**
* 将不同的枚举项和处理策略注册上
* @return
*/
@Override
protected EnumProcessorRegister<MiaoAliPayStatusEnum, String, String> register() {
return new EnumProcessorRegister<MiaoAliPayStatusEnum,String, String>()
.register(MiaoAliPayStatusEnum.SUCCESS,new PayStatusEnumHandler());
}
}
ok,这就完事了,需要注意的是,我这里只注册了MiaoAliPayStatusEnum.SUCCESS
枚举项对应的处理策略,在项目启动的时候将此类创建加载到Spring容器的时候就会报错,提示你没有对所有的枚举项配置相应的处理策略,如下图:
Caused by: java.lang.RuntimeException: PayStatusEnumMatcher don't config all enum processor,FAIL is missing
at club.hellojava.enums.processor.AbstractEnumChecker.validateAllEnumItemMatchProcessor(AbstractEnumChecker.java:24)
at club.hellojava.enums.processor.AbstractEnumChecker.registerCheck(AbstractEnumChecker.java:15)
at club.hellojava.enums.processor.AbstractEnumChecker.<init>(AbstractEnumChecker.java:9)
at club.hellojava.enums.processor.AbstractEnumMatcher.<init>(AbstractEnumMatcher.java:8)
at club.hellojava.enums.processor.PayStatusEnumMatcher.<init>(PayStatusEnumMatcher.java:10)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:170)
... 19 common frames omitted
Disconnected from the target VM, address: '127.0.0.1:56226', transport: 'socket'
注意:校验逻辑依靠构造代码块,我在此类上加了@Component,Spring在启动的时候会去创建这个类,也就会执行对应的构造代码块了,如果不依赖Spring,那么保证此类在项目启动的时候会初始化即可。
下面把所有的策略都注册上看看效果:
@Component
public class PayStatusEnumMatcher extends AbstractEnumMatcher<MiaoAliPayStatusEnum,String,String> {
/**
* 返回要处理的枚举类型
*/
@Override
public Class<MiaoAliPayStatusEnum> getEnumType() {
return MiaoAliPayStatusEnum.class;
}
/**
* 将不同的枚举项和处理策略注册上
* @return
*/
@Override
protected EnumProcessorRegister<MiaoAliPayStatusEnum, String, String> register() {
return new EnumProcessorRegister<MiaoAliPayStatusEnum,String, String>()
.register(MiaoAliPayStatusEnum.SUCCESS,new PayStatusEnumHandler())
.register(MiaoAliPayStatusEnum.FAIL, s -> "打款失败")
.register(MiaoAliPayStatusEnum.ING, s -> "正在打款中,请稍后");
}
}
代码中用了Lambda表达式,这样就不用每次都实现一个接口了。
使用:
public static void main(String[] args) {
String result = new PayStatusEnumMatcher().matchAndProcess(MiaoAliPayStatusEnum.FAIL, "张三");
System.out.println(result);
}
结果:
张三 打款失败
这样当我们新增一个枚举项而忘了定义它的处理策略的时候,项目启动不起来,便于我们直接发现问题。
此外,新增枚举项的处理策略,只需新增一个实现IEnumProcessor接口的实现类,然后注册到注册器里面即可,满足对增加开放,对修改关闭的哲学。
网友评论