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

Design pattern to refactor code with nested if else and switch statements

我必须重构庞大的现有代码。在 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,因为它们在一些相关的公共代码库中使用。

编辑:这是此代码中使用的方法。我尽了我最大的努力让它变得简单,但无法想出任何可行的想法。

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;
        }
    }

你在实际代码中重复了很多东西。
在考虑您可以使用的模式之前,我建议您开始删除实际的 真正的问题 :作为反模式的代码重复。

minor 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().
的第一个参数 用 Budget 枚举替换您在 switch 语句中使用的字符串,这部分也可以缩短为:

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()
您可以为您复制的每件东西应用完全相同的配方,其中真正的差异是您可以传递的参数。
因此,与其打开第二个下拉菜单的可能性("Day" "Week","Month","Quarter","Year") 引入一个枚举来传达这个信息:

TimePeriod timePeriod = TimePeriod.valueOf(second_Drop_Down_Value);

对第三个下拉菜单("Details" 和 "Summary")做同样的事情:

DetailLevel detailLevel = DetailLevel.valueOf(third_Drop_Down_Value);

然后根据TimePeriod和选择的DetailLevel给出改变fetch粒度的方法。它只是在检索数据的方法中传递的两个参数。

通过在此处停止您的重构任务,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 语句的情况下调用不同的设置器的部分,您仍然有许多相似之处可以分解。
以案例为例:"TOP DOWN" 和 "ORIGINAL" :

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 显示为 classes,其中有很多单独的字段可以甚至应该提取到特定的 classes 中,因为它们之间是相关的:它是高内聚原则。

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

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

对于 "ORIGINAL" 转储,您可以定义:

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

而每个 "dump part" 的 TemplateDump 现在可能看起来像:

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

但是复制所有这些 "Dump" class 是否有意义,而它们将拥有完全相同的结构?不是真的。
您可能应该将它们提取到基础 class :

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():"-");
// ...