简体   繁体   English

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

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

I have to refactor a bulky existing code. 我必须重构一个庞大的现有代码。 Gone through a lot of similar questions on SO and other sites, still confused. 在SO和其他网站上经历了很多类似的问题,仍然感到困惑。 If anyone can put some light or any idea will be of great help. 如果有人可以放些亮点或者任何想法都会有很大的帮助。

There are 5 drop downs and I need to update the view based upon the slected values in drop downs. 有5个下拉,我需要根据下拉列表中的选定值更新视图。 First drop down has following options: 首先下拉有以下选项:

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

Second drop down has following options: 第二次下拉有以下选项:

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

Third drop down has following options: 第三次下拉有以下选项:

"Details"
"Summary"

Fourth drop down has following options: 第四次下拉有以下选项:

"Hours"
"Dollars"

Fifth drop down has following options: 第五次下拉有以下选项:

 "StartDate"
 "EndDate"

Now code has following scenario: 现在代码有以下场景:

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());

} }

This code is more than 1000 lines, and I am deseparate to refactor it using some suitable design pattern. 这段代码超过1000行,我要使用一些合适的设计模式来重构它。

WorkPlanReport & TaskDetails DTOs have few similar types of properties in common and many other different properties also. WorkPlanReport和TaskDetails DTO几乎没有相似类型的属性和许多其他不同的属性。

I am not allowed to alter those DTOs because those are used in some related common code base. 我不被允许改变那些DTO,因为它们在一些相关的公共代码库中使用。

EDIT: This is a method being used in this code. 编辑:这是在此代码中使用的方法。 I tried my level best to make it simple but couldn't come up with any working idea. 我尽力使自己的水平变得简单,但无法提出任何有用的想法。

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

You duplicate many things in the actual code. 您在实际代码中复制了很多东西。
Before thinking about patterns you could use, I advise you to start removing the actual real issue : the code duplication that is an anti pattern. 在考虑您可以使用的模式之前,我建议您开始删除实际的实际问题 :代码重复是一种反模式。

The minor dup is the first level switch statement : 次要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;
 ...

Almost all is duplicated. 几乎所有都是重复的。 The single variant thing is the first parameter passed to getTimeLine() . 单一变体是传递给getTimeLine()的第一个参数。
Replace the String that you use in the switch statement by a Budget enum and this part could be also short as : 通过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);

And seemingly the big duplication is located in getTimeLine() . 似乎重复的大部分位于getTimeLine()
You could apply exactly the same recipe for each thing that you duplicate and where the single real differences are parameters that you could pass. 您可以为复制的每个事物应用完全相同的配方,并且单个实际差异是您可以传递的参数。
So instead of switching on the possibilities of the second dropdown ("Day" "Week","Month","Quarter","Year") introduce an enum to convey this information: 因此,不是打开第二个下拉菜单(“Day”,“Week”,“Month”,“Quarter”,“Year”)的可能性,而是引入一个枚举来传达这些信息:

TimePeriod timePeriod = TimePeriod.valueOf(second_Drop_Down_Value);

Do the same thing for the third dropdown ("Details" and "Summary") : 对第三个下拉列表执行相同的操作(“详细信息”和“摘要”):

DetailLevel detailLevel = DetailLevel.valueOf(third_Drop_Down_Value);

And then give a way to change the granularity of the fetch according to the TimePeriod and the DetailLevel selected. 然后根据TimePeriod和所选的DetailLevel提供一种方法来更改提取的粒度。 It will just be two parameters passed in the method that retrieve the data. 它只是在检索数据的方法中传递的两个参数。

By stopping here your refactoring task the getTimeLine() looks like really shorter and simpler : 通过在这里停止你的重构​​任务, 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
        }
    }   
}

Edit 编辑

About the part where you invoke distinct setters in the cases of the switch statement, you still have many similarities that you can factor out. 关于在switch语句的情况下调用不同setter的部分,您仍然可以分解许多相似之处。
Take by example the cases : "TOP DOWN" and "ORIGINAL" : 以示例为例:“TOP DOWN”和“ORIGINAL”:

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

versus

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

Then : 然后 :

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

versus : 与 :

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

And so for... 所以......

Finally, the same logic is applied but the computations are not assigned to the same setters in WorkPlanReport and TemplateDump objects . 最后,应用了相同的逻辑,但计算未分配给WorkPlanReportTemplateDump对象中的相同setter。
WorkPlanReport and TemplateDump appear as classes with a lot of individual fields that could and even should be extracted into specific classes because these are related between them : it is the principle of high cohesion. WorkPlanReportTemplateDump显示为具有大量单个字段的类,这些字段甚至可以提取到特定类中,因为它们之间是相关的:它是高内聚的原则。

For example for the "TOP DOWN" dump , you could define : 例如,对于“TOP DOWN”转储,您可以定义:

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

For the "ORIGINAL" dump , you could define : 对于“ORIGINAL”转储,您可以定义:

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

And the TemplateDump that holds each "dump part" could look like now : 并且保存每个“转储部件”的TemplateDump现在看起来像:

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

But does it make sense to duplicate all these "Dump" classes while they will own exactly the same structure ? 但复制所有这些“转储”类是否有意义,而它们将拥有完全相同的结构? Not really. 并不是的。
You should probably extract them into a base class : 您应该将它们提取到基类中:

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

Concrete parts can now inherit from AbstractTemplateDump such as : 具体部件现在可以从AbstractTemplateDump继承,例如:

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

Now you have an uniform way to set the data of the TemplateDump instance. 现在,您可以使用统一的方法来设置TemplateDump实例的数据。 So the switch is not required any longer. 因此不再需要switch
Follow exactly the same logic for WorkPlanReport . 遵循与WorkPlanReport完全相同的逻辑。

Your code could look like now : 您的代码现在看起来像:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM