设计模式可使用嵌套if语句和switch语句重构代码

时间:2018-07-27 11:38:49

标签: java design-patterns switch-statement refactoring nested-if

我必须重构大量现有代码。在SO和其他网站上解决了许多类似的问题,但仍然感到困惑。如果有人可以提出一些建议或任何想法,将大有帮助。

有5个下拉菜单,我需要根据下拉菜单中的选定值更新视图。 第一个下拉菜单具有以下选项:

"TOP DOWN BUDGET"
"TEMPLATE BUDGET"
"ORIGINAL BUDGET"
"REVISED BUDGET"

第二个下拉菜单具有以下选项:

"Day"
"Week"
"Month"
"Quarter"
"Year"

第三下拉菜单有以下选项:

"Details"
"Summary"

第四下拉菜单具有以下选项:

"Hours"
"Dollars"

第五个下拉菜单具有以下选项:

 "StartDate"
 "EndDate"

现在的代码具有以下情形:

public List<WorkPlanReport> XYZ(...){//opening of some method XYZ....

List<WorkPlanReport> workPlanReportList=null;
switch(first_Drop_Down_Value){

    case "TOP DOWN BUDGET":
        List<TaskDetails> timeLine=getTimeLine("firstDropDownA", second_drop_down_val, third_drop_down_val, fourth_drop_down_val);
        workPlanReportList=setWorkPlanByTimeLine(timeLine, "firstDropDownA", second_drop_down_val, third_drop_down_val, fourth_drop_down_val);
        break;
    case "TEMPLATE BUDGET":
        List<TaskDetails> timeLine=getTimeLine("firstDropDownB", second_drop_down_val, third_drop_down_val, fourth_drop_down_val, fifth_dd_val);
        workPlanReportList=setWorkPlanByTimeLine(timeLine, "firstDropDownA", second_drop_down_val, third_drop_down_val, fourth_drop_down_val);
        break;
    case "ORIGINAL BUDGET":
        List<TaskDetails> timeLine=getTimeLine("firstDropDownC", second_drop_down_val, third_drop_down_val, fourth_drop_down_val, fifth_dd_val);
        workPlanReportList=setWorkPlanByTimeLine(timeLine, "firstDropDownA", second_drop_down_val, third_drop_down_val, fourth_drop_down_val);
        break;
    case "REVISED BUDGET":
        List<TaskDetails> timeLine=getTimeLine("firstDropDownD", second_drop_down_val, third_drop_down_val, fourth_drop_down_val, fifth_dd_val);
        workPlanReportList=setWorkPlanByTimeLine(timeLine, "firstDropDownA", second_drop_down_val, third_drop_down_val, fourth_drop_down_val);
        break;

}

return workPlanReportList;
}// Closing of some method XYZ....

private List<TaskDetails> getTimeLine(String first_Drop_Down_Value, String second_dd_val, third_dd_val, fourth_dd_val, String fifth_dd_val){

    switch(second_Drop_Down_Value){

    case "Day":
        if(third_dd_val.equals("Details")){
            if(fourth_dd_val.equals("Hours")){
                if(fifth_dd_val.equals("startDate"){
                //prepare query & call DB for fetching days timeline for hours details of TOP DOWN BUDGET filtered by start date...
                }
                else// means endDate{
                //prepare query & call DB for fetching days timeline for hours details of TOP DOWN BUDGET filtered by end date...
                }
            }
            else{//means Dollars
                if(fifth_dd_val.equals("startDate"){
                //prepare query & call DB for fetching days timeline for dollars details of TOP DOWN BUDGET filtered by start date...
                }
                else// means endDate{
                //prepare query & call DB for fetching days timeline for dollars details of TOP DOWN BUDGET filtered by end date...
                }
            }   
        }
        else// means summary...
        {
            if(fourth_dd_val.equals("Hours")){
                if(fifth_dd_val.equals("startDate"){
                //prepare query & call DB for fetching days timeline for 'hours' "summary" of TOP DOWN BUDGET filtered by start date...
                }
                else// means endDate{
                //prepare query & call DB for fetching days timeline for hours summary of TOP DOWN BUDGET filtered by end date...
                }
            }
            else{//means Dollars
                if(fifth_dd_val.equals("startDate"){
                //prepare query & call DB for fetching days timeline for dollars details of TOP DOWN BUDGET filtered by start date...
                }
                else// means endDate{
                //prepare query & call DB for fetching days timeline for dollars details of TOP DOWN BUDGET filtered by end date...
                }
            }
        }
        break;
    case "Week":
            //....similar code as in case "Day" just here we need to fetch data week-wise
            break;
    case "Month":
            //....similar code as in case "Day" just here we need to fetch data month-wise
            break;
    case "Quarter":
            //....similar code as in case "Day" just here we need to fetch data quarter-wise
            break;
    case "Year":
            //....similar code as in case "Day" just here we need to fetch data year-wise
            break;
    }
}


private List<WorkPlanReport> setWorkPlanByTimeLine(List<TaskDetails> timeLine, String "firstDropDownA", String second_drop_down_val, String third_drop_down_val, String fourth_drop_down_val){

WorkPlanReport wpr=new WorkPlanReport();
// Here I have real mess..., Iterating the timeLine list and have switch case and inside switch case multilevel nesting of if else to decide which setter we need to use to set the value.

for example:

If it is "TOP DOWN BUDGET" in first drop-down, "Day" in second drop-down, "Details" in third drop down, "Hours" in fourth drop-down & "StartDate" in fifth drop-down, I have to call follwing code:

wpr.setTDBDetailsHoursByStartDate(taskDetails.getTDBDetailsByHourStartDayView());

If it is "TOP DOWN BUDGET" in first drop-down, "Day" in second drop-down, "Details" in third drop down, "Hours" in fourth drop-down & "EndDate" in fifth drop-down, I have to call follwing code:
wpr.setTDBDetailsHoursByEndDate(taskDetails.getTDBDetailsByHourEndDayView());

}

这段代码超过1000行,我不愿使用一些合适的设计模式来对其进行重构。

WorkPlanReport和TaskDetails DTO在通用属性中几乎没有相似类型的属性,在其他许多属性中也是如此。

由于某些相关的通用代码库中使用了DTO,所以我不允许更改这些DTO。

编辑:这是此代码中使用的方法。我尽力使级别变得简单,但无法提出任何可行的想法。

private void setSummaryColumns(String DolOrHr, String columnFilter,
            Map<String, String> filterBy, Object[] obj,
            WorkplanReport workplanReport, TemplateDump td) {
        switch(filterBy.get(columnFilter)){
        case "TOP DOWN":
            td.setTopDownBudgetStartDate(obj[1] != null ? (Date)obj[1]:null);
            td.setTopDownBudgetEndDate(obj[2] != null ? (Date)obj[2]:null);
             workplanReport.setStartDatebyMajorMeasure(obj[1] != null ? obj[1].toString():"-");
             workplanReport.setEndDatebyMajorMeasure(obj[2] != null ? obj[2].toString():"-");
             workplanReport.setDolorHrsbyMajorMeasure(obj[3] != null ? obj[3].toString():"-");
            if(DolOrHr.equals("BudgetDollars"))
                td.setTopDownBudgetDollars(obj[3] != null ? (BigDecimal)obj[3]:null);

            else
                td.setTopDownBudgetHours(obj[3] != null ? (BigDecimal)obj[3]:null);
            break;
        case "TEMPLATE":
            td.setTemplateBudgetStartDate(obj[1] != null ? (Date)obj[1]:null);
            td.setTemplateBudgetEndDate(obj[2] != null ? (Date)obj[2]:null);
            workplanReport.setStartDatebyTemplateBudget(obj[1] != null ? obj[1].toString():"-");
            workplanReport.setEndDatebyTemplateBudget(obj[2] != null ? obj[2].toString():"-");
            workplanReport.setDolorHrsbyTemplateBudget(obj[3] != null ? obj[3].toString():"-");
            if(DolOrHr.equals("BudgetDollars"))
                td.setTemplateBudgetDollars(obj[3] != null ? (BigDecimal)obj[3]:null);
            else
                td.setTemplateBudgetHours(obj[3] != null ? (BigDecimal)obj[3]:null);
            break;
        case "ORIGINAL":
            td.setOriginalBudgetStartDate(obj[1] != null ? (Date)obj[1]:null);
            td.setOriginalBudgetEndDate(obj[2] != null ? (Date)obj[2]:null);
            workplanReport.setOrgStartDate(obj[1] != null ? obj[1].toString():"-");
            workplanReport.setOrgEndDate(obj[2] != null ? obj[2].toString():"-");
            workplanReport.setOrgBudgetedDollarsHours(obj[3] != null ? obj[3].toString():"-");
            if(DolOrHr.equals("BudgetDollars"))
                td.setOriginalBudgetDollars(obj[3] != null ? (BigDecimal)obj[3]:null);
            else
                td.setOriginalBudgetHours(obj[3] != null ? (BigDecimal)obj[3]:null);
            break;
        case "REVISED":
            td.setRevisedBudgetStartDate(obj[1] != null ? (Date)obj[1]:null);
            td.setRevisedBudgetEndDate(obj[2] != null ? (Date)obj[2]:null);
            workplanReport.setRevStartDate(obj[1] != null ? obj[1].toString():"-");
            workplanReport.setRevEndDate(obj[2] != null ? obj[2].toString():"-");
            workplanReport.setRevBudgetedDollarsHours(obj[3] != null ? obj[3].toString():"-");
            if(DolOrHr.equals("BudgetDollars"))
                td.setRevisedBudgetDollars(obj[3] != null ? (BigDecimal)obj[3]:null);
            else
                td.setRevisedBudgetHours(obj[3] != null ? (BigDecimal)obj[3]:null);
            break;

        case "MANAGER":

            td.setManagerBudgetStartDate(obj[1] != null ? (Date)obj[1]:null);
            td.setManagerBudgetEndDate(obj[2] != null ? (Date)obj[2]:null);
            workplanReport.setStartDatebyManagersRevised(obj[1] != null ? obj[1].toString():"-");
            workplanReport.setEndDatebyManagersRevised(obj[2] != null ? obj[2].toString():"-");
            workplanReport.setDolorHrsbyManagersRevised(obj[3] != null ? obj[3].toString():"-");
            if(DolOrHr.equals("BudgetDollars"))
                td.setManagerBudgetDollars(obj[3] != null ? (BigDecimal)obj[3]:null);
            else
                td.setManagerBudgetHours(obj[3] != null ? (BigDecimal)obj[3]:null);
            break;
        case "ACTUAL":
            td.setActualBudgetStartDate(obj[1] != null ? (Date)obj[1]:null);
            td.setActualBudgetEndDate(obj[2] != null ? (Date)obj[2]:null);
            workplanReport.setStartDatebyActual(obj[1] != null ? obj[1].toString():"-");
            workplanReport.setEndDatebyActual(obj[2] != null ? obj[2].toString():"-");
            workplanReport.setDolorHrsbyActual(obj[3] != null ? obj[3].toString():"-");
            if(DolOrHr.equals("BudgetDollars"))
                td.setActualBudgetDollars(obj[3] != null ? (BigDecimal)obj[3]:null);
            else
                td.setActualBudgetHours(obj[3] != null ? (BigDecimal)obj[3]:null);
            break;
        }
    }

1 个答案:

答案 0 :(得分:4)

您在实际代码中重复了很多事情。
在考虑可以使用的模式之前,我建议您开始消除实际的实际问题:这是一种反模式的代码重复。

次要dup是第一级switch语句:

case "TOP DOWN BUDGET":
    List<TaskDetails> timeLine=getTimeLine("firstDropDownA", second_drop_down_val, third_drop_down_val, fourth_drop_down_val);
    workPlanReportList=setWorkPlanByTimeLine(timeLine, "firstDropDownA", second_drop_down_val, third_drop_down_val, fourth_drop_down_val);
    break;
case "TEMPLATE BUDGET":
    List<TaskDetails> timeLine=getTimeLine("firstDropDownB", second_drop_down_val, third_drop_down_val, fourth_drop_down_val, fifth_dd_val);
    workPlanReportList=setWorkPlanByTimeLine(timeLine, "firstDropDownA", second_drop_down_val, third_drop_down_val, fourth_drop_down_val);
    break;
 ...

几乎所有重复项。 唯一的变体是传递给getTimeLine()的第一个参数。
将您在switch语句中使用的字符串替换为Budget枚举,该部分也可能简短为:

Budget budget = Budget.valueOf(first_Drop_Down_Value);

List<TaskDetails> timeLine=getTimeLine(budget.getValueForTimeLine(), second_drop_down_val, third_drop_down_val, fourth_drop_down_val);
workPlanReportList=setWorkPlanByTimeLine(timeLine, "firstDropDownA", second_drop_down_val, third_drop_down_val, fourth_drop_down_val);

似乎最大的重复项位于getTimeLine()中。
您可以为要复制的每件物品应用完全相同的配方,其中唯一的实际差异是您可以传递的参数。
因此,与其打开第二个下拉菜单(“天” “周”,“月”,“季度”,“年”)引入了一个枚举来传达此信息:

TimePeriod timePeriod = TimePeriod.valueOf(second_Drop_Down_Value);

对第三个下拉列表(“详细信息”和“摘要”)执行相同的操作:

DetailLevel detailLevel = DetailLevel.valueOf(third_Drop_Down_Value);

然后提供一种方法,可以根据选择的TimePeriodDetailLevel更改获取的粒度。只是检索数据的方法中传递的两个参数。

通过在此处停止您的重构任务,getTimeLine()看起来确实更短,更简单:

private List<TaskDetails> getTimeLine(String first_Drop_Down_Value, String second_dd_val, third_dd_val, fourth_dd_val, String fifth_dd_val){

    TimePeriod timePeriod = TimePeriod.valueOf(second_Drop_Down_Value);
    DetailLevel detailLevel = DetailLevel.valueOf(third_Drop_Down_Value);

    if(fourth_dd_val.equals("Hours")){
        if(fifth_dd_val.equals("startDate"){           
          fetch(...,....,timePeriod, detailLevel) // <- pass the enums
        }
        else// means endDate{
        //prepare query & call DB for fetching days timeline for hours details of TOP DOWN BUDGET filtered by end date...
          fetch(...,....,timePeriod, detailLevel) // <- pass the enums
        }
    }
    else{//means Dollars
        if(fifth_dd_val.equals("startDate"){
        //prepare query & call DB for fetching days timeline for dollars details of TOP DOWN BUDGET filtered by start date...
          fetch(...,....,timePeriod, detailLevel) // <- pass the enums
        }
        else// means endDate{
        //prepare query & call DB for fetching days timeline for dollars details of TOP DOWN BUDGET filtered by end date...
          fetch(...,....,timePeriod, detailLevel) // <- pass the enums
        }
    }   
}

修改

关于在switch语句中调用不同的二传手的部分,您仍然有许多相似之处可以排除。
以示例为例:“自上而下”和“原始”:

td.setTopDownBudgetStartDate(obj[1] != null ? (Date)obj[1]:null);
td.setTopDownBudgetEndDate(obj[2] != null ? (Date)obj[2]:null);

td.setOriginalBudgetStartDate(obj[1] != null ? (Date)obj[1]:null);
td.setOriginalBudgetEndDate(obj[2] != null ? (Date)obj[2]:null);

然后:

workplanReport.setStartDatebyMajorMeasure(obj[1] != null ? obj[1].toString():"-");
workplanReport.setEndDatebyMajorMeasure(obj[2] != null ? obj[2].toString():"-");
workplanReport.setDolorHrsbyMajorMeasure(obj[3] != null ? obj[3].toString():"-");

与:

workplanReport.setOrgStartDate(obj[1] != null ? obj[1].toString():"-");
workplanReport.setOrgEndDate(obj[2] != null ? obj[2].toString():"-");
workplanReport.setOrgBudgetedDollarsHours(obj[3] != null ? obj[3].toString():"-");

等等...

最后,应用了相同的逻辑,但是没有将计算分配给WorkPlanReportTemplateDump对象中的相同设置器。
WorkPlanReportTemplateDump出现为具有许多单独字段的类,这些字段可以甚至应该提取到特定的类中,因为它们之间是相关的:这是高凝聚力的原理。

例如,对于“ TOP DOWN”转储,您可以定义:

public class TopDownTemplateDump {
    private Date budgetStartDate;
    private Date budgetEndDate;
    private BigDecimal budgetDollars;
    private BigDecimal budgetHours;
    // and so for ...
    // setters - getters 
}

对于“原始”转储,您可以定义:

public class OriginalTemplateDump {
    private Date budgetStartDate;
    private Date budgetEndDate;
    private BigDecimal budgetDollars;
    private BigDecimal budgetHours;
    // and so for ...
    // setters - getters
}     

包含每个“转储部分”的TemplateDump可能现在看起来像这样:

public class TemplateDump {
   private TopDownTemplateDump topDownTemplateDump;
   private OriginalTemplateDump originalTemplateDump;
   ...
}

但是复制所有这些“转储”类而它们将拥有完全相同的结构是否有意义?不是真的
您可能应该将它们提取到基类中:

public abstract class AbstractTemplateDump {
    private Date budgetStartDate;
    private Date budgetEndDate;
    private BigDecimal budgetDollars;
    private BigDecimal budgetHours;
    // and so for ...
    // setters - getters
}       

混凝土零件现在可以从AbstractTemplateDump继承,例如:

public class TopDownTemplateDump extends AbstractTemplateDump{
    // add subclass specifities here
}

现在,您可以采用统一的方式来设置TemplateDump实例的数据。因此,不再需要switch
WorkPlanReport遵循完全相同的逻辑。

您的代码现在看起来像:

AbstractTemplateDump absTd = td.getDumpPart(filterBy.get(columnFilter));
AbstractReportPart absRp = workplanReport.getReportPart(filterBy.get(columnFilter));

absTd.setBudgetStartDate(obj[1] != null ? (Date)obj[1]:null);
absTd.setBudgetEndDate(obj[2] != null ? (Date)obj[2]:null);
absRp.setStartDateby(obj[1] != null ? obj[1].toString():"-");
absRp.setEndDateby(obj[2] != null ? obj[2].toString():"-");
absRp.setDolorHrsby(obj[3] != null ? obj[3].toString():"-");
// ...