简体   繁体   中英

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. 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. 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.

WorkPlanReport & TaskDetails DTOs have few similar types of properties in common and many other different properties also.

I am not allowed to alter those DTOs because those are used in some related common code base.

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 :

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() .
Replace the String that you use in the switch statement by a Budget enum and this part could be also short as :

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

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. 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 :

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.
Take by example the cases : "TOP DOWN" and "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 .
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.

For example for the "TOP DOWN" dump , you could define :

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 :

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 :

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 :

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

Now you have an uniform way to set the data of the TemplateDump instance. So the switch is not required any longer.
Follow exactly the same logic for 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():"-");
// ...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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