用Map的方式消除if-else的进阶方案

场景

在SpringBoot项目中,有一个表,内部有n种数据类型,每种数据的生成逻辑有区别,若是用if-else或者switch-case的方式,判断链回显得比较冗余

实现

借助函数式接口,我们可以将方法存入到Map中,在调用时,只需要根据类型获取对应的方法,并且执行该方法即可获取到需要生成的数据

在函数式接口的基础上,引入泛型,做一个通用类,通用类中封装了Map的put、get方法,我们可以很方便的调用,即可完成逻辑的统一

函数式接口采用 java.util.function.Function,通用类的泛型会传递到函数式接口中,指定了

  • 通用类

    /**
     * @author iAuzre 2024/8/15 11:09
     */
    public class InitBaseData<T, R> {
    
        private static final Logger log = LoggerFactory.getLogger(InitBaseData.class);
    
        private final Map<String, Function<T, List<R>>> map = new ConcurrentHashMap<>();
    
        protected void register(String key, Function<T, List<R>> function) {
            if (Objects.isNull(function)) {
                log.warn("function is null! key:{}", key);
                return;
            }
            map.put(key, function);
        }
    
        protected Function<T, List<R>> getFunction(String type) {
            return map.get(type);
        }
    }
    
    
  • 子类实现

    /**
     * 管会分析-人工成本及手续费用表(MaLaborHandlingFee)应用服务
     *
     * @author iAuzre 2024-08-15 15:51:53
     */
    @Service
    public class MaLaborHandlingFeeServiceImpl extends InitBaseData<String, MaLaborHandlingFee> implements MaLaborHandlingFeeService {
    
        private static final Logger log = LoggerFactory.getLogger(MaLaborHandlingFeeServiceImpl.class);
    
        private final MaLaborHandlingFeeRepository maLaborHandlingFeeRepository;
    
        @Autowired
        public MaLaborHandlingFeeServiceImpl(MaLaborHandlingFeeRepository maLaborHandlingFeeRepository) {
            this.maLaborHandlingFeeRepository = maLaborHandlingFeeRepository;
        }
    
        /**
         * 将各种类型基础数据生成逻辑注册到函数式接口 Map 中
         * <p>
         * 注册、获取方法来自于父类 {@link InitBaseData}
         * <p>
         * 其中,父类封装了注册、获取方法提供使用
         */
        @PostConstruct
        public void registerBaseDataFunction() {
            register(MaLaborHandlingFee.TableType.TABLE1.getName(), maLaborHandlingFeeRepository::initDataTable1);
            register(MaLaborHandlingFee.TableType.TABLE2.getName(), maLaborHandlingFeeRepository::initDataTable2);
            register(MaLaborHandlingFee.TableType.TABLE3.getName(), maLaborHandlingFeeRepository::initDataTable3);
            register(MaLaborHandlingFee.TableType.TABLE4.getName(), maLaborHandlingFeeRepository::initDataTable4);
            register(MaLaborHandlingFee.TableType.TABLE5.getName(), maLaborHandlingFeeRepository::initDataTable5);
        }
    
        @Override
        public Page<MaLaborHandlingFee> selectList(PageRequest pageRequest, MaLaborHandlingFee maLaborHandlingFee) {
            Page<MaLaborHandlingFee> page = PageHelper.doPageAndSort(pageRequest,
                    () -> maLaborHandlingFeeRepository.selectList(maLaborHandlingFee));
    
            if (CollectionUtils.isNotEmpty(page)) {
                return page;
            }
    
            // 查询数据为空,根据表类型、期间生成基础数据
            initBaseData(maLaborHandlingFee.getTableType(), maLaborHandlingFee.getGlPeriod());
    
            // 重查返回数据
            return PageHelper.doPageAndSort(pageRequest, () -> maLaborHandlingFeeRepository.selectList(maLaborHandlingFee));
        }
    
        /**
         * 根据数据类型、期间生成基础数据
         * <p>
         * 需注意:为防止重复生成数据,只能生成一次,方法加了操作锁,同时内部会校验对应数据是否已经生成
         * <p>
         * 通过 insert 数据之前校验数据数量来乐观的保证微服务架构下锁成功
         *
         * @param tableType 数据类型{@link MaLaborHandlingFee.TableType}
         * @param glPeriod  期间
         */
        private synchronized void initBaseData(String tableType, String glPeriod) {
            Function<String, List<MaLaborHandlingFee>> function = getFunction(tableType);
            Objects.requireNonNull(function, "表类型异常或对应方法未注册成功!表类型:" + tableType);
            List<MaLaborHandlingFee> baseList = function.apply(glPeriod);
    
            // 获取数据数量,若不为0,则跳过insert
            int dataCount = maLaborHandlingFeeRepository.selectCountByCondition(
                    Condition.builder(MaLaborHandlingFee.class)
                            .where(Sqls.custom()
                                    .andEqualTo(MaLaborHandlingFee.FIELD_TABLE_TYPE, tableType)
                                    .andEqualTo(MaLaborHandlingFee.FIELD_GL_PERIOD, glPeriod))
                            .build());
            if (dataCount != 0) {
                log.warn("存在重复生成数据情况,跳过insert, tableType:{}, glPeriod:{}", tableType, glPeriod);
                return;
            }
    
            maLaborHandlingFeeRepository.batchInsertSelective(baseList);
        }
    }
    

    我们在使用的时候继承该通用父类,并指定函数式接口的入参、返回值的泛型对应值,即可完成父类内的初始化

    上述代码中:initBaseData方法使用了synchronized主要是为了防止高并发场景下重复生成数据的问题,但单用synchronized来做锁并不能限制住微服务场景,为了解决这一问题,在我们准备insert之前,还查询了一次表中该类型数据的数量以防止重复实现,算是分布式场景下的一种乐观锁实现方式。