干掉Switch


优化目标: 去掉Switch

背景介绍

对于前端页面的筛选查询,查询条件具有不确定性,不同的字段可能使用的条件也是不同的,需要对不同的字段创建不同的查询条件。

  • 查询:使用MyBatsiPlus 的 QueryWrapper 进行单表查询。

  • 根据JSONObject 对象中的不同的Key调用不同的QueryWrapper 对象方法,组织查询条件。

优化前

/**
 * @author <a href="mailto:1595550476@qq.com">KawYang</a>
 * Created by MacBook Pro on 2021/05/06.
 */
@Service
public class ThirdPartLogServiceImpl implements ThirdPartLogService {

    @Autowired
    private RequestLogMapper requestLogMapper;

       // 模糊查询列表方法
    @Override
    public List<LogInfo> getList(JSONObject jsonObject, Integer page, Integer size) {

        // 根据JSONObject组织 QueryWrapper
        QueryWrapper<RequestLog> queryWrapper = new QueryWrapper<>();
        makeQuery(jsonObject, queryWrapper);

        queryWrapper.orderByAsc("id");
        queryWrapper.last("limit " +((page-1) * size) + "," +size);

        return requestLogMapper.selectList(queryWrapper).stream()
                .peek(System.out::println)
                .map(RequestLog::transToResponse)
                .collect(Collectors.toList());
    }

      // 待优化的代码块 - 去掉 代码中的 switch 
    private void makeQuery(JSONObject jsonObject, QueryWrapper queryWrapper) {
        jsonObject.keySet().stream()
                  // 过滤掉错误条件
                  .filter(e->jsonObject.getObject(e, Object.class) != null && !jsonObject.getString(e).isEmpty()
        ).forEach(e ->{
            switch (e){
                case "endTime":
                    queryWrapper.le(keyMap.get(e), jsonObject.getObject(e, Date.class)); break;
                case "startTime":
                    queryWrapper.ge(keyMap.get(e), jsonObject.getObject(e, Date.class)); break;
                default:
                    queryWrapper.eq(keyMap.get(e), jsonObject.getObject(e, String.class)); break;
            }

        });
    }

    /**
     * jsonObject.key -> table.key
     * 将 JSONObject 的 Key 转换成 表 中的Kay
     */
    private static final Map<String, String> keyMap = new HashMap<String, String>(){
        {
            put("startTime", "add_time");
            put("endTime", "add_time");
            put("doctor", "doctor");
            put("subType", "sub_type");
            put("status", "status");
            put("backStatus", "back_status");
        }
    };

}

分析: Switch max 执行的次数为 : jsonObject 的 Kay 的数量 与 swatch 的case 数量的 笛卡尔积 时间复杂度为 $O(n^2)$

优化: 由于 startTime 和 endTime 只处理一次, 可以先处理,再用循环处理默认方法 ->将时间复杂度降成 $O(n)$.

函数式接口的定义: 执行的方法queryWrapper.le(keyMap.get(e), jsonObject.getObject(e, Date.class))中有三个参数 , 其中 e 在 前两个case 中可以写死,所以可以需要定义两个参数。方法中不需要返回值,可以选择java8 的 BiConsumer函数接口。

@FunctionalInterface
public interface BiConsumer<T, U>{
void accept(T t, U u);
...
}

初次优化

      // 先定义函数式接口  - 不同的 Key 值调用不同的方法
    private BiConsumer<JSONObject, QueryWrapper> FN1 = (json, queryWrapper) -> queryWrapper.le(keyMap.get("endTime"), json.getObject("endTime", Date.class));
    private BiConsumer<JSONObject, QueryWrapper> FN2 = (json, queryWrapper) -> queryWrapper.ge(keyMap.get("startTime"), json.getObject("startTime", Date.class));

    private void makeQuery(JSONObject jsonObject, QueryWrapper queryWrapper) {
          // 先将  startTime 和 endTime 进行处理
          if (jsonObject.containsKey("endTime")) FN1.accept(jsonObject, queryWrapper);
        if (jsonObject.containsKey("startTime")) FN2.accept(jsonObject, queryWrapper);
        jsonObject.keySet().stream()
                      // 过滤掉已处理
                .filter(d -> d != "endTime" && d != "startTime")
                      // 过滤掉错误数据
                .filter(e-> jsonObject.getObject(e, Object.class) != null && !jsonObject.getString(e).isEmpty())
                      // 未处理的组织 queryWrapper
                .forEach(e -> queryWrapper.eq(keyMap.get(e), jsonObject.getObject(e, String.class)));
    }

    /**
     * jsonObject.key -> table.key
     */
    private static final Map<String, String> keyMap = new HashMap<String, String>(){
        {
            put("startTime", "add_time");
            put("endTime", "add_time");
            put("doctor", "doctor");
            put("subType", "sub_type");
            put("status", "status");
            put("backStatus", "back_status");
        }
    };

}

缺点: 只将前两个 case 转换成函数式方法,默认处理方法未处理,导致内容不够整洁。

再次优化


    /**
     * 组织查询条件
     */
    private final Consumer3<JSONObject, QueryWrapper<RequestLog>, String> FN1 = (json, queryWrapper, key) -> queryWrapper.le(keyMap.get(key), json.getObject(key, Date.class));
    private final Consumer3<JSONObject, QueryWrapper<RequestLog>, String> FN2 = (json, queryWrapper, key) -> queryWrapper.ge(keyMap.get(key), json.getObject(key, Date.class));
    private final Consumer3<JSONObject, QueryWrapper<RequestLog>, String> FN3 = (json, queryWrapper, key) -> queryWrapper.eq(keyMap.get(key), json.getString(key));

        // 使用表驱动的方式
    private final  HashMap<String, Consumer3<JSONObject, QueryWrapper<RequestLog>, String>>
        funcMap = new HashMap<String, Consumer3<JSONObject, QueryWrapper<RequestLog>, String>>(){
        {
            put("endTime", FN1);
            put("startTime", FN2);
            put("other", FN3);
        }
    };


    private void makeQuery(JSONObject jsonObject, QueryWrapper<RequestLog> queryWrapper) {

        jsonObject.keySet().stream()
                      // 筛选出符合条件的数据
                .filter(e -> jsonObject.getObject(e, Object.class) != null && !jsonObject.getString(e).isEmpty())
                      //
                .forEach(e ->{
                    if(funcMap.containsKey(e)) funcMap.get(e).accept(jsonObject, queryWrapper,e);
                    else funcMap.get("other").accept(jsonObject, queryWrapper,e);
                });
    }

总结: 采用表驱动的方式,将不同的查询条件,分发到不同的函数式方法中,完成条件的拼接。

在 makeQuery 方法中 遍历一次就可完成 条件的拼接。时间复杂度为 $O(n)$


文章作者: KawYang
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 KawYang !
评论
  目录