用枚举+Lambda 优化代码

业务需求: 1、统计4个业务指标的分别按照本月,上月、去年本月的日期维度的数据 2、其中本月需要额外按照每周统计数据,最终数据以一个二维表格形式展示

大致效果如下:

Alt text

原代码

private void calcBIData(List<SystemCalendar> currentMonthCalendarList,
        List<ReportUserProjectBusinessIndicators> currentMonthBIList,
        List<ReportUserProjectBusinessIndicators> lastMonthBIList,
        List<ReportUserProjectBusinessIndicators> lastYearThisMonthBIList,
        ProjectStatisticsReportResult result){
        ProjectStatisticsReportByDetailResult newRequirementData = new ProjectStatisticsReportByDetailResult();
        ProjectStatisticsReportByDetailResult numberOfExternalRecruitmentExpertsData = new ProjectStatisticsReportByDetailResult();
        ProjectStatisticsReportByDetailResult numberOfRecommendedExpertsData = new ProjectStatisticsReportByDetailResult();
        ProjectStatisticsReportByDetailResult numberOfCheckedActivitysData = new ProjectStatisticsReportByDetailResult();
        Map<LocalDate,List<ReportUserProjectBusinessIndicators>> currentMonthBIMap = currentMonthBIList.stream()
            .collect(Collectors.groupingBy(r->r.getDateTime().toLocalDate()));
        //先计算本月的数据
        int[][] currentMonthWeekData = new int[5][4];
        for(int[] item:currentMonthWeekData){
            Arrays.fill(item,0);
        }
        int[] currentMonthTotalData = new int[]{0,0,0,0};
        for(SystemCalendar calendar:currentMonthCalendarList){
            List<ReportUserProjectBusinessIndicators> BIList = currentMonthBIMap.get(calendar.getDate().toLocalDate());
            if(CollectionUtils.isEmpty(BIList)){
                continue;
            }
            int index = calendar.getWeekOfMonth()-1;
            for(ReportUserProjectBusinessIndicators BI:BIList){
                currentMonthWeekData[index][0] = currentMonthWeekData[index][0] + BI.getNumberOfCreateRequirement();
                currentMonthWeekData[index][1] = currentMonthWeekData[index][1] + BI.getNumberOfExternalRecruitmentExperts();
                currentMonthWeekData[index][2] = currentMonthWeekData[index][2] + BI.getNumberOfRecommendedExperts();
                currentMonthWeekData[index][3] = currentMonthWeekData[index][3] + BI.getNumberOfCheckedActivitys();
                currentMonthTotalData[0] = currentMonthTotalData[0] + BI.getNumberOfCreateRequirement();
                currentMonthTotalData[1] = currentMonthTotalData[1] + BI.getNumberOfExternalRecruitmentExperts();
                currentMonthTotalData[2] = currentMonthTotalData[2] + BI.getNumberOfRecommendedExperts();
                currentMonthTotalData[3] = currentMonthTotalData[3] + BI.getNumberOfCheckedActivitys();
            }
        }
        //计算上月的数据
        int[] lastMonthTotalData = new int[]{0,0,0,0};
        for(ReportUserProjectBusinessIndicators BI:lastMonthBIList){
            lastMonthTotalData[0] = lastMonthTotalData[0] + BI.getNumberOfCreateRequirement();
            lastMonthTotalData[1] = lastMonthTotalData[1] + BI.getNumberOfExternalRecruitmentExperts();
            lastMonthTotalData[2] = lastMonthTotalData[2] + BI.getNumberOfRecommendedExperts();
            lastMonthTotalData[3] = lastMonthTotalData[3] + BI.getNumberOfCheckedActivitys();
        }
        //计算去年的数据
        int[] lastYearThisMonthData = new int[]{0,0,0,0};
        for(ReportUserProjectBusinessIndicators BI:lastYearThisMonthBIList){
            lastYearThisMonthData[0] = lastYearThisMonthData[0] + BI.getNumberOfCreateRequirement();
            lastYearThisMonthData[1] = lastYearThisMonthData[1] + BI.getNumberOfExternalRecruitmentExperts();
            lastYearThisMonthData[2] = lastYearThisMonthData[2] + BI.getNumberOfRecommendedExperts();
            lastYearThisMonthData[3] = lastYearThisMonthData[3] + BI.getNumberOfCheckedActivitys();
        }
        //计算各业务指标的同比环比
        newRequirementData.setFifthWeekDataOfCurrentMonth(new BigDecimal(currentMonthWeekData[0][0]));
        newRequirementData.setSecondWeekDataOfCurrentMonth(new BigDecimal(currentMonthWeekData[1][0]));
        newRequirementData.setThirdWeekDataOfCurrentMonth(new BigDecimal(currentMonthWeekData[2][0]));
        newRequirementData.setFourthWeekDataOfCurrentMonth(new BigDecimal(currentMonthWeekData[3][0]));
        newRequirementData.setFifthWeekDataOfCurrentMonth(new BigDecimal(currentMonthWeekData[4][0]));
        newRequirementData.setTotalDataOfCurrentMonth(new BigDecimal(currentMonthTotalData[0]));
        newRequirementData.setLastMonthData(new BigDecimal(lastMonthTotalData[0]));
        newRequirementData.setLastYearThisMonthData(new BigDecimal(lastYearThisMonthData[0]));
        newRequirementData.setMonthOnMonth(BusinessReportUtils.calcMonthOnMonth(
            newRequirementData.getTotalDataOfCurrentMonth(),newRequirementData.getLastMonthData()));
        newRequirementData.setYearOnYear(BusinessReportUtils.calcYearOnYear(
            newRequirementData.getTotalDataOfCurrentMonth(),newRequirementData.getLastYearThisMonthData()));

        numberOfExternalRecruitmentExpertsData.setFifthWeekDataOfCurrentMonth(new BigDecimal(currentMonthWeekData[0][1]));
        numberOfExternalRecruitmentExpertsData.setSecondWeekDataOfCurrentMonth(new BigDecimal(currentMonthWeekData[1][1]));
        numberOfExternalRecruitmentExpertsData.setThirdWeekDataOfCurrentMonth(new BigDecimal(currentMonthWeekData[2][1]));
        numberOfExternalRecruitmentExpertsData.setFourthWeekDataOfCurrentMonth(new BigDecimal(currentMonthWeekData[3][1]));
        numberOfExternalRecruitmentExpertsData.setFifthWeekDataOfCurrentMonth(new BigDecimal(currentMonthWeekData[4][1]));
        numberOfExternalRecruitmentExpertsData.setTotalDataOfCurrentMonth(new BigDecimal(currentMonthTotalData[1]));
        numberOfExternalRecruitmentExpertsData.setLastMonthData(new BigDecimal(lastMonthTotalData[1]));
        numberOfExternalRecruitmentExpertsData.setLastYearThisMonthData(new BigDecimal(lastYearThisMonthData[1]));
        numberOfExternalRecruitmentExpertsData.setMonthOnMonth(BusinessReportUtils.calcMonthOnMonth(
            numberOfExternalRecruitmentExpertsData.getTotalDataOfCurrentMonth(),numberOfExternalRecruitmentExpertsData.getLastMonthData()));
        numberOfExternalRecruitmentExpertsData.setYearOnYear(BusinessReportUtils.calcYearOnYear(
            numberOfExternalRecruitmentExpertsData.getTotalDataOfCurrentMonth(),numberOfExternalRecruitmentExpertsData.getLastYearThisMonthData()));

        numberOfRecommendedExpertsData.setFifthWeekDataOfCurrentMonth(new BigDecimal(currentMonthWeekData[0][2]));
        numberOfRecommendedExpertsData.setSecondWeekDataOfCurrentMonth(new BigDecimal(currentMonthWeekData[1][2]));
        numberOfRecommendedExpertsData.setThirdWeekDataOfCurrentMonth(new BigDecimal(currentMonthWeekData[2][2]));
        numberOfRecommendedExpertsData.setFourthWeekDataOfCurrentMonth(new BigDecimal(currentMonthWeekData[3][2]));
        numberOfRecommendedExpertsData.setFifthWeekDataOfCurrentMonth(new BigDecimal(currentMonthWeekData[4][2]));
        numberOfRecommendedExpertsData.setTotalDataOfCurrentMonth(new BigDecimal(currentMonthTotalData[2]));
        numberOfRecommendedExpertsData.setLastMonthData(new BigDecimal(lastMonthTotalData[2]));
        numberOfRecommendedExpertsData.setLastYearThisMonthData(new BigDecimal(lastYearThisMonthData[2]));
        numberOfRecommendedExpertsData.setMonthOnMonth(BusinessReportUtils.calcMonthOnMonth(
            numberOfRecommendedExpertsData.getTotalDataOfCurrentMonth(),numberOfRecommendedExpertsData.getLastMonthData()));
        numberOfRecommendedExpertsData.setYearOnYear(BusinessReportUtils.calcYearOnYear(
            numberOfRecommendedExpertsData.getTotalDataOfCurrentMonth(),numberOfRecommendedExpertsData.getLastYearThisMonthData()));

        numberOfCheckedActivitysData.setFifthWeekDataOfCurrentMonth(new BigDecimal(currentMonthWeekData[0][3]));
        numberOfCheckedActivitysData.setSecondWeekDataOfCurrentMonth(new BigDecimal(currentMonthWeekData[1][3]));
        numberOfCheckedActivitysData.setThirdWeekDataOfCurrentMonth(new BigDecimal(currentMonthWeekData[2][3]));
        numberOfCheckedActivitysData.setFourthWeekDataOfCurrentMonth(new BigDecimal(currentMonthWeekData[3][3]));
        numberOfCheckedActivitysData.setFifthWeekDataOfCurrentMonth(new BigDecimal(currentMonthWeekData[4][3]));
        numberOfCheckedActivitysData.setTotalDataOfCurrentMonth(new BigDecimal(currentMonthTotalData[3]));
        numberOfCheckedActivitysData.setLastMonthData(new BigDecimal(lastMonthTotalData[3]));
        numberOfCheckedActivitysData.setLastYearThisMonthData(new BigDecimal(lastYearThisMonthData[3]));
        numberOfCheckedActivitysData.setMonthOnMonth(BusinessReportUtils.calcMonthOnMonth(
            numberOfCheckedActivitysData.getTotalDataOfCurrentMonth(),numberOfCheckedActivitysData.getLastMonthData()));
        numberOfCheckedActivitysData.setYearOnYear(BusinessReportUtils.calcYearOnYear(
            numberOfCheckedActivitysData.getTotalDataOfCurrentMonth(),numberOfCheckedActivitysData.getLastYearThisMonthData()));

		//设置返回结果
        result.setNewRequirementData(newRequirementData);
        result.setNumberOfCheckedActivitysData(numberOfCheckedActivitysData);
        result.setNumberOfExternalRecruitmentExpertsData(numberOfExternalRecruitmentExpertsData);
        result.setNumberOfRecommendedExpertsData(numberOfRecommendedExpertsData);

    }

原代码存在问题:

  1. 可读性很差,各种通过下标方式访问累加器,不方便理解代码。
  2. 不方便维度,如果后续需要调整日期维度或数据维度,很难修改。

优化后的代码

定义一个int类型的累加器:


/**
 * int类型累加器
 */
public class IntAccumulator {

    /**
     * 当前的值
     */
    private int value;

    /**
     * 构造一个累加器
     */
    public IntAccumulator(int initValue){
        this.value = initValue;
    }

    /**
     * 获取当前值
     * @return int
     */
    public int getValue(){
        return value;
    }
    /**
     * 累加值
     * @param value
     * @return void
     */
    public void accumulate(Integer value){
        if(value==null){
            return;
        }
        this.value += value;
    }

    /**
     * 累加值
     * @param value
     * @return void
     */
    public void accumulate(int value){
        this.value += value;
    }

}

定义数据维度的枚举

在枚举里增加一个Lambda表达式,用于获取每个数据维度的数据的处理方法。


 /**
   * 项目统计报表的数据类型枚举
   */
  private enum ProjectStatisticsReportDataType{
      NEW_REQUIREMENT(ReportUserProjectBusinessIndicators::getNumberOfCreateRequirement),
      NUMBER_OF_EXTERNAL_RECRUITMENT_EXPERTS(ReportUserProjectBusinessIndicators::getNumberOfExternalRecruitmentExperts),
      NUMBER_OF_RECOMMENDED_EXPERTS(ReportUserProjectBusinessIndicators::getNumberOfRecommendedExperts),
      NUMBER_OF_CHECKED_ACTIVITYS(ReportUserProjectBusinessIndicators::getNumberOfCheckedActivitys),

      ;

      /**
       * 构建一个项目统计报表的数据类型枚举
       * @param getBIDataFun
       * @return
       */
      private ProjectStatisticsReportDataType(Function<ReportUserProjectBusinessIndicators,Integer> getBIDataFun){
          this.getBIDataFun = getBIDataFun;
      }

      /**
       * 获取业务数据的方法
       */
      private Function<ReportUserProjectBusinessIndicators,Integer> getBIDataFun;

      /**
       * 获取业务数据
       * @param businessIndicators
       * @return java.lang.Integer
       */
      private Integer getBIData(ReportUserProjectBusinessIndicators businessIndicators){
          return getBIDataFun.apply(businessIndicators);
      }

  }

定义日期维度的枚举

在枚举里增加一个Lambda表达式,用于设置每个日期维度的统计结果的值。


 /**
   * 项目统计报表的日期类型枚举
   */
  private enum ProjectStatisticsReportDateType{
      CURRENT_MONT((r,v)->r.setTotalDataOfCurrentMonth(new BigDecimal(v))),
      LAST_MONTH((r,v)->r.setLastMonthData(new BigDecimal(v))),
      LAST_YEAR_THIS_MONTH((r,v)->r.setLastYearThisMonthData(new BigDecimal(v))),
      ;

      /**
       * 项目统计报表的日期类型枚举
       * @param setDataFun
       */
      private ProjectStatisticsReportDateType(
          BiConsumer<ProjectStatisticsReportByDetailResult,Integer> setDataFun){
          this.setDataFun = setDataFun;
      }
      /**
       * 设置数据的方法
       */
      private BiConsumer<ProjectStatisticsReportByDetailResult,Integer> setDataFun;

      /**
       * 设置数据
       * @param result
       * @param value
       * @return void
       */
      public void setData(ProjectStatisticsReportByDetailResult result,int value){
          this.setDataFun.accept(result,value);
      }

  }

定义本月第几周类型的枚举

在枚举里增加一个Lambda表达式,用于设置每周的统计结果的值。


/**
  * 项目统计报表的本月第几周类型枚举
  */
 private enum ProjectStatisticsReportCurrentMonthWeek{
     FIRST((r,v)->r.setFirstWeekDataOfCurrentMonth(new BigDecimal(v))),
     SECOND((r,v)->r.setSecondWeekDataOfCurrentMonth(new BigDecimal(v))),
     THIRD((r,v)->r.setThirdWeekDataOfCurrentMonth(new BigDecimal(v))),
     FOURTH((r,v)->r.setFourthWeekDataOfCurrentMonth(new BigDecimal(v))),
     FIFTH((r,v)->r.setFifthWeekDataOfCurrentMonth(new BigDecimal(v))),
     ;

     /**
      * 项目统计报表的本月第几周类型枚举
      * @param setDataFun
      */
     private ProjectStatisticsReportCurrentMonthWeek(
         BiConsumer<ProjectStatisticsReportByDetailResult,Integer> setDataFun){
         this.setDataFun = setDataFun;
     }
     /**
      * 设置数据的方法
      */
     private BiConsumer<ProjectStatisticsReportByDetailResult,Integer> setDataFun;

     /**
      * 设置数据
      * @param result
      * @param value
      * @return void
      */
     public void setData(ProjectStatisticsReportByDetailResult result,int value){
         this.setDataFun.accept(result,value);
     }
 }

新的统计代码

private Map<ProjectStatisticsReportDataType,ProjectStatisticsReportByDetailResult> getAndCalcBIData(
    List<SystemCalendar> currentMonthCalendarList,
    List<ReportUserProjectBusinessIndicators> currentMonthBIList,
    List<ReportUserProjectBusinessIndicators> lastMonthBIList,
    List<ReportUserProjectBusinessIndicators> lastYearThisMonthBIList){
    //转行日期Map
    Map<LocalDate,SystemCalendar> currentMonthCalendarMap = currentMonthCalendarList.stream()
        .collect(Collectors.toMap(r->r.getDate().toLocalDate(),Function.identity()));
    //定义累加器
    Map<ProjectStatisticsReportDateType,Map<ProjectStatisticsReportDataType, IntAccumulator>> monthAccumulatorMap
        =Stream.of(ProjectStatisticsReportDateType.values())
        .collect(Collectors.toMap(Function.identity(),r->getInitDataTypeAccumulatorMap()));
    Map<ProjectStatisticsReportCurrentMonthWeek,Map<ProjectStatisticsReportDataType, IntAccumulator>> weekAccumulatorMap
        =Stream.of(ProjectStatisticsReportCurrentMonthWeek.values())
        .collect(Collectors.toMap(Function.identity(),r->getInitDataTypeAccumulatorMap()));
    //定义报表明细结果
    Map<ProjectStatisticsReportDataType,ProjectStatisticsReportByDetailResult> reportDetailMap
        =Stream.of(ProjectStatisticsReportDataType.values())
        .collect(Collectors.toMap(Function.identity(),r->new ProjectStatisticsReportByDetailResult()));
    //先计算本月的数据
    currentMonthBIList.forEach(r->{
        SystemCalendar calendar = currentMonthCalendarMap.get(r.getDateTime().toLocalDate());
        int index = calendar.getWeekOfMonth()-1;
        weekAccumulatorMap.get(ProjectStatisticsReportCurrentMonthWeek.values()[index])
            .forEach((k,v)->{
                v.accumulate(k.getBIData(r));
            });
        monthAccumulatorMap.get(ProjectStatisticsReportDateType.CURRENT_MONT)
            .forEach((k,v)->{
                v.accumulate(k.getBIData(r));
            });
    });
    //计算上月的数据
    lastMonthBIList.forEach(r->{
        monthAccumulatorMap.get(ProjectStatisticsReportDateType.LAST_MONTH)
            .forEach((k,v)->{
                v.accumulate(k.getBIData(r));
            });
    });
    //计算去年的数据
    lastYearThisMonthBIList.forEach(r->{
        monthAccumulatorMap.get(ProjectStatisticsReportDateType.LAST_YEAR_THIS_MONTH)
            .forEach((k,v)->{
                v.accumulate(k.getBIData(r));
            });
    });
    //填充数据
    fillData(reportDetailMap,monthAccumulatorMap,weekAccumulatorMap);
    return reportDetailMap;
}


/**
  *  填充数据
  * @param reportDetailMap
  * @param monthAccumulatorMap
  * @param weekAccumulatorMap
  * @return void
  */
 private void fillData(Map<ProjectStatisticsReportDataType,ProjectStatisticsReportByDetailResult> reportDetailMap,
     Map<ProjectStatisticsReportDateType, Map<ProjectStatisticsReportDataType, IntAccumulator>> monthAccumulatorMap,
     Map<ProjectStatisticsReportCurrentMonthWeek,Map<ProjectStatisticsReportDataType, IntAccumulator>> weekAccumulatorMap){
     weekAccumulatorMap.forEach((k,v)->{
         v.forEach((k2,v2)->{
             k.setData(reportDetailMap.get(k2),v2.getValue());
         });
     });
     monthAccumulatorMap.forEach((k,v)->{
         v.forEach((k2,v2)->{
             k.setData(reportDetailMap.get(k2),v2.getValue());
         });
     });
     //计算同比环比
     reportDetailMap.forEach((k,v)->{
         v.setMonthOnMonth(BusinessReportUtils.calcMonthOnMonth(
             v.getTotalDataOfCurrentMonth(),v.getLastMonthData()));
         v.setYearOnYear(BusinessReportUtils.calcYearOnYear(
             v.getTotalDataOfCurrentMonth(),v.getLastYearThisMonthData()));
     });
 }

优化说明:

  1. 增加了业务指标枚举,并在枚举类设置了取相应指标的方法,将同一个业务规则封装到同一块代码里,后续如果需要增加新的业务指标,只需要在枚举里增加新的枚举,同时设置取新的指标的数据的方法即可,方便了以后的扩展。
  2. 增加日期月维度的枚举,并在枚举类设置修改枚举对应的报表结果的数据的方法,方便了以后的扩展。
  3. 增加了本月第几周的枚举,并在枚举类设置修改枚举对应的报表结果的数据的方法,方便了以后的扩展。
  4. 通过构造以上3个枚举之后,实际的报表数据统计代码就不用再考虑具体的业务指标规则了,只需要写好统计的代码,并调用不同枚举的方法即可,将业务的统计代码和实际的业务指标数据隔离。即实现了方法和数据的分离。
  5. 最终效果可读性和可维护性大大提升。