Reputation: 5733
I use this code in my application and I find it very ugly. Is there a smart way of doing this?
for (final ApplicationCategories applicationCategorie : applicationCategories) {
if (applicationCategorie == ApplicationCategories.PROJECTS) {
// invoke right method
} else if (applicationCategorie == ApplicationCategories.CALENDAR) {
// ...
} else if (applicationCategorie == ApplicationCategories.COMMUNICATION) {
} else if (applicationCategorie == ApplicationCategories.CONTACTS) {
} else if (applicationCategorie == ApplicationCategories.DOCUMENTS) {
} else if (applicationCategorie == ApplicationCategories.WORKINGBOOK) {
}
}
My aim is to handle all application categorie enums which contained into the enum list.
Upvotes: 3
Views: 816
Reputation: 1001
The design pattern you need is the Strategy.
The use of enums when you have to perform a different action for each defined value is a bad practice. As the software evolves, it is likely that you have to spread the if chain around different places. If you add a new enum value, you'll have to add a new if for that value in all these places. Since you may not even be able to find all the places where you have to include the new if, that is a source for bugs.
Such approach also violates the Open/Closed Principle (OCP). Just creating a method to handle each enum value doesn't make your code conformant to OCP. It will make the code more organised but doesn't change anything about the "if" issue.
Using Java 7 or prior, you can define a ApplicationCategory interface that all categories will implement. This interface will provide a common method that each category will implement to perform the required actions for that category:
public interface ApplicationCategory {
boolean handle();
}
Usually your method should return something. Since I don't know what is your exact goal, I'm making the method to return just a boolean. It would indicate if the category was handled or not, just as an example.
Then you have to define a class implementing such an interface for each category you have. For instance:
public class CalendarCategory implements ApplicationCategory {
boolean handle(){
//the code to handle the Calendar category
return true;
}
}
public class CommunicationCategory implements ApplicationCategory {
boolean handle(){
//the code to handle the Communication category
return true;
}
}
Now you don't need the enum class and the handle method that was inside it needs to be moved elsewhere, that completely depends on your project. That handle
method will be changed to:
public static void handle(ApplicationCategory category) {
//Insert here any code that may be executed,
//regardless of what category it is.
category.handle();
}
You don't need an enum anymore because any variable declared as ApplicationCategory
just accepts an object that implements such an interface. If you use the enum together with the Strategy implementation, it will be yet required to change the enum class any time you add a new ApplicationCategory
implementation, violating the OCP again.
If you use the Strategy pattern, you don't even need the enum anymore in this case.
You can more easily implement the Strategy pattern using functional programming and lambda expressions, and avoid the proliferation of class just to provide different implementations of a single method (the handle
method in this case).
Since the handle
method is not receiving any parameter and is retuning something, this description conforms to the Supplier functional interface. An excellent way to identify what kind of functional interface a method you are defining conforms to, it is studying the java.util.function package.
Once the type of functional interface is identified, we can create just a ApplicationCategory
class (that in the Java 7 example was an interface) in a functional way, defining the previous handle
method as an attribute of the Supplier
type. You must define a setter for this handle attribute to enable changing the handle implementation. Defining a method as an attribute, you are enabling such a method implementation to be changed in runtime, providing a different but far simpler, easier and more maintainable implementation of the Strategy pattern.
If you need to use the category name somewhere, for instance to display it in a user interface, you could define an enum inside the ApplicationCategory
class. However, there is no direct relation between the enum value and the handle
provided. The enum works just as a tag for the category. It is like a "name" attribute in a Person
class, that we usually just use to "tag" and print a person.
public class ApplicationCategory {
//insert all categories here
enum Type {CALENDAR, COMMUNNICATION}
/**
* The Supplier object that will handle this category object.
* It will supply (return) a boolean to indicate if the category
* was processed or not.
*/
private Supplier<Boolean> handler;
private Type type;
/**
* A constructor that will receive a Supplier defining how to
* handle the category that is being created.
*/
public ApplicationCategory(Type type, Supplier<Boolean> handler){
Objects.requireNonNull(type);
this.handler = handler;
setType(type);
}
/**
* Handle the category by calling the {@link Supplier#get()} method,
* that in turn returns a boolean.
*/
public boolean handle(){
return supplier.get();
}
public Type getType(){ return type; }
public final void setHandler(Supplier<Boolean> handler){
Objects.requiredNonNull(handler);
this.handler = handler;
}
}
If you give the behaviour that will handle the enum value at the enum constructor call, as suggested by the other answer provided here, then you don't have how to change the behaviour in runtime and it in fact doesn't conform to the Strategy pattern. Unless you really don't need that, you might implement in that way, but remember it violates the OCP.
To instantiate a ApplicationCategory you have to provide the Type (an enum value) and the handler (that is a Supplier
and can be given as a lambda expression). See the example below:
import static ApplicationCategory.CALENDAR;
public class Test {
public static void main(String args[]){
new Test();
}
public Test(){
ApplicationCategory cat = new ApplicationCategory(CALENDAR, this::calendarHandler);
System.out.println("Was " + cat + " handled? " + cat.handle());
}
private boolean calendarHandler(){
//the code required to handle the CALENDAR goes here
return true;
}
}
The this::calendarHandler
instruction is a method reference to pass a "pointer" to the calendarHandler
method. It is not calling the method (you can see that due to the use of :: instead of . and the lack of parenthesis), it is just defining what method has to be in fact called when the handle()
method is called, as can be seen in System.out.println("Was " + cat + " handled? " + cat.handle());
By using this approach, it is possible to define different handlers for different instances of the same category or to use the same handler for a subset of categories.
Upvotes: 4
Reputation: 59174
Unlike other languages, Java provides facilities that specifically allow this sort of thing to be done in a safe object-oriented way.
Declare an abstract method on the enum, and then specific implementations for each enum constant. The compiler will then ensure that every constant has an implementation and nobody has to worry about missing a case somewhere as new values are added:
enum ApplicationCategories {
PROJECTS {
void handle() {
...
}
},
...
public abstract void handle();
}
Then, instead of calling some static handle(category)
, you just call category.handle()
Upvotes: 2
Reputation: 8353
The least you can do is to declare the method that handles the behaviour dependent to the enum
inside ApplicationCategories
. In this way, if you will add a new value to the enum, you will only to change the code relative to enum.
In this way, your code adheres to the Open Closed Principle, and so it is easier to maintain.
enum ApplicationCategories {
PROJECTS,
CALENDAR,
// And so on...
WORKINGBOOK;
public static void handle(ApplicationCategories category) {
switch (category) {
case PROJECTS:
// Code to handle projects
break;
case CALENDAR:
// Code to handle calendar
break;
// And so on
}
}
}
This solution is only feasable if you do not need any external information to handle the enum value.
Remember you can also add fields to enum values.
EDIT
You can also implement a Strategy design pattern if you need. First of all, define a strategy interface and some concrete implementations.
interface CategoryStrategy {
void handle(/* Some useful input*/);
}
class ProjectStrategy implements Strategy {
public void handle(/* Some useful input*/) {
// Do something related to projects...
}
}
class CalendarStrategy implements Strategy {
public void handle(/* Some useful input*/) {
// Do something related to calendars...
}
}
//...
Then, you can modify your enum in order to use the above strategy.
enum ApplicationCategories {
PROJECTS(new ProjectStrategy()),
CALENDAR(new CalendarStrategy()),
// And so on...
WORKINGBOOK(new WorkingBookStrategy());
private CategoryStrategy strategy;
ApplicationCategories(CategoryStrategy strategy) {
this.strategy = strategy;
}
public static void handle(ApplicationCategories category) {
category.strategy.handle(/* Some inputs */);
}
}
Clearly, the above code is only a sketch.
Upvotes: 5