Reputation: 1279
I have this large switch case that I really don't like, but I don't seem to find an elegant replacement solution. We are building a JavaEE platform where users can create Projects. The method listed is used for determining a status message that is displayed to our users, which depends on many factors, such as the user type (we have 2), the project state, the payment state of the project and many more. Most of these factors have to be determined programmatically, which ended up in having this large switch-case:
public List<String> getToDoMessages(Project project) {
UserAccount user = securitySession.getLoggedInAccount();
switch (user.getAccountType()) {
case EXPERT:
return getExpertToDoMessages(project, user);
case COMPANY:
return getCompanyToDoMessages(project, user);
default:
return new ArrayList<>();
}
}
private List<String> getCompanyToDoMessages(Project project, UserAccount user) {
List<String> ret = new ArrayList<>();
switch (project.getProjectState()) {
case OPEN_FOR_APPLICATION:
ret.add("projectToDo_company_applicationDeadlineNotPassed");
break;
case SELECT_APPLICATION:
ret.add("projectToDo_company_selectApplicant");
break;
case IN_PROGRESS:
ret.add("projectToDo_company_inProgress");
break;
case TEAM_PAYMENT_DISTRIBUTION:
ret.add("projectToDo_company_teamPaymentDistribution");
break;
case CONFIRM_INVOICES:
if (projectAssessmentService.hasAssessed(user, project)) {
ret.add("projectToDo_company_confirmInvoices");
} else {
ret.add("projectToDo_company_assessProject");
}
break;
default:
break;
}
return ret;
}
private List<String> getExpertToDoMessages(Project project, UserAccount user) {
ExpertPerson expert = securitySession.getExpert();
List<String> ret = new ArrayList<>();
switch (project.getProjectState()) {
case OPEN_FOR_APPLICATION:
if (projectService.hasAlreadyApplied(expert, project)) {
if (projectService.hasAlreadyAppliedAsPerson(expert, project)) {
ret.add("projectToDo_expert_appliedAsSinglePerson");
}
if (projectService.hasAlreadyAppliedAsTeam(expert, project)) {
ret.add("projectToDo_expert_appliedAsTeam");
}
} else {
if (projectService.canApply(expert, project)) {
ret.add("projectToDo_expert_openForApplication");
}
}
break;
case SELECT_APPLICATION:
ret.add("projectToDo_expert_selectApplicant");
break;
case IN_PROGRESS:
ret.add("projectToDo_expert_inProgress");
break;
case TEAM_PAYMENT_DISTRIBUTION:
Application application = project.getSelectedApplication();
if (application.isSingleApplication()) {
throw new IllegalStateException("Illegal state TEAM_PAYMENT_DISTRIBUTION for project that has selected a single application");
}
ExpertTeam team = application.getExpertTeam();
if (team.getLeader().equals(expert)) {
ret.add("projectToDo_expert_teamLeaderPaymentDistribution");
} else {
ret.add("projectToDo_expert_teamMemberPaymentDistribution");
}
break;
case CONFIRM_INVOICES:
if (projectAssessmentService.hasAssessed(user, project)) {
ret.add("projectToDo_expert_confirmInvoices");
} else {
ret.add("projectToDo_expert_assessProject");
}
break;
default:
break;
}
return ret;
}
This version doesn't have all possibilities listed, the distinction of the project type for example is still missing. Of course, I could at least move the code inside the statements into separate methods, but I'm sure there has to be a more elegant solution. Does anyone know a good pattern that might be applicable here?
Thanks in advance!
Upvotes: 3
Views: 4566
Reputation: 65811
There are three good routes to take for refactoring switch
statements.
Map
. This allows you to pre-build the tooling and can often even allow you to configure the tooling from external configuration files. Downsides here are that you have to jump through some hoops to add logic.enum
. This can be more flexible than a Map
because you can code logic inside each enum
but there are some downsides. Some feel that coding logic in an enum
is a bad thing (I do not). Also, the fact that enum
s can only be static
can make you work a little less easy.Project
object have a getToDoMessages
method etc. This can lead to some quite complex management issues as all of the getToDoMessages
methods are distributed throughout your code rather than in one module as you have them now - but please consider this option as a good one as it is often the most flexible one.Example of the Map
route:
Map<Integer,String> companyToDos = new HashMap<>();
static {
companyToDos.put(OPEN_FOR_APPLICATION, "projectToDo_company_applicationDeadlineNotPassed");
companyToDos.put(SELECT_APPLICATION, "projectToDo_company_selectApplicant");
companyToDos.put(IN_PROGRESS, "projectToDo_company_inProgress");
companyToDos.put(TEAM_PAYMENT_DISTRIBUTION, "projectToDo_company_teamPaymentDistribution");
companyToDos.put(CONFIRM_INVOICES_ASSESSED, "projectToDo_company_confirmInvoices");
companyToDos.put(CONFIRM_INVOICES_UNASSESSED, "projectToDo_company_assessProject");
}
Example of taking the enum
route:
enum AccountType {
EXPERT{
@Override
List<String> getToDoMessages(Project project) {
return project.getState().getExpertToDoMessages();
}
},
COMPANY{
@Override
List<String> getToDoMessages(Project project) {
return project.getState().getCompanyToDoMessages();
}
};
abstract List<String> getToDoMessages(Project project);
}
enum ProjectState {
OPEN_FOR_APPLICATION{
@Override
List<String> getExpertToDoMessages(Project project) {
return ...
}
@Override
List<String> getCompanyToDoMessages(Project project) {
return ...
}
},
SELECT_APPLICATION{
@Override
List<String> getExpertToDoMessages(Project project) {
return ...
}
@Override
List<String> getCompanyToDoMessages(Project project) {
return ...
}
};
abstract List<String> getExpertToDoMessages(Project project);
abstract List<String> getCompanyToDoMessages(Project project);
}
Upvotes: 5