美文网首页
如何优雅的解决项目中到处Switch Case 枚举导致的项目质

如何优雅的解决项目中到处Switch Case 枚举导致的项目质

作者: 炎族族长炎天帝 | 来源:发表于2020-04-19 22:07 被阅读0次

前言

最近发现,当一个项目越做越大的时候,会有很多的Switch Case枚举的情况,当这个枚举不加新的枚举项的时候还好,如果新加了一个,那真的是噩梦,项目中到处散落的Switch Case都要修改,这会导致什么问题?

  1. 在某处的Switch Case忘记新增的枚举项,编译和启动的时候并不会报错,只有当线上执行到这块代码时才会出问题,然后重新打包上线
  2. 修改了,但是影响了以前的代码,简单来说,不是对修改关闭

简单的例子

有下面一个枚举:

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就是一枚举项。

解决思路

分析一下上面的情况,解决思路如下:

  1. 如果新增的枚举项没有对应的处理策略,则让项目启动不起来,保证各个枚举项都要被处理。
  2. 将不同的处理策略聚拢在一起,防止到处修改代码导致项目不可控

模块分析

总体分四块:

  1. 每个枚举项中的处理策略
  2. 校验每个枚举项是否都绑定到处理策略的校验模块
  3. 根据枚举项映射到对应执行策略的匹配模块
  4. 处理策略注册模块
image.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接口的实现类,然后注册到注册器里面即可,满足对增加开放,对修改关闭的哲学。

相关文章

网友评论

      本文标题:如何优雅的解决项目中到处Switch Case 枚举导致的项目质

      本文链接:https://www.haomeiwen.com/subject/dduibhtx.html