I have two enums:
public enum MainMenuOptions {
EXIT("Exit"),
VIEW_RESERVATIONS("View Reservations By Host"),
CREATE_RESERVATION("Create A Reservation"),
EDIT_RESERVATION("Edit A Reservation"),
CANCEL_RESERVATION("Cancel A Reservation");
private final String message;
MainMenuOptions(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
public static List<String> asListString() {
return Arrays.stream(MainMenuOptions.values())
.map(MainMenuOptions::getMessage)
.collect(Collectors.toList());
}
}
public enum HostSelectionMethodOptions {
FIND_ALL("Find all"),
FIND_BY_LASTNAME_PREFIX("Find by last name prefix"),
FIND_BY_CITY_STATE("Find by city & state");
String message;
HostSelectionMethod(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
public static List<String> asListString() {
return Arrays.stream(HostSelectionMethod.values())
.map(HostSelectionMethod::getMessage)
.collect(Collectors.toList());
}
}
Both enums share the same field
private final String message;
The same getter
public String getMessage() {
return message;
}
And the same asListString() method
public static List<String> asListString() {
return Arrays.stream(MainMenuOptions.values())
.map(MainMenuOptions::getMessage)
.collect(Collectors.toList());
}
I expect to have more enums with the same fields and methods, and it seems silly to write out the same thing over and over again for each one.
The flavor I was hoping the code could have is something like this:
public class Utils {
public static List<String> enumAsListString(Enum e) {
return e.values().stream.map(e::getMessage).collect(Collectors.toList());
}
}
This is probably one of the cases where you need to pick one between being DRY and using enums.
Enums don't go very far as far as code reuse is concerned, in Java at least; and the main reason for this is that primary benefits of using enums are reaped in static code - I mean static as in "not dynamic"/"runtime", rather than static
:). Although you can "reduce" code duplication, you can hardly do much of that without introducing dependency (yes, that applies to adding a common API/interface, extracting the implementation of asListString
to a utility class). And that's still an undesirable trade-off.
Furthermore, if you must use an enum (for such reasons as built-in support for serialization, database mapping, JSON binding, or, well, because it's data enumeration, etc.), you have no choice but to duplicate method declarations to an extent, even if you can share the implementation: static methods just can't be inherited, and interface methods (of which getMessage
would be one) shall need an implementation everywhere. I mean this way of being "DRY" will have many ways of being inelegant.
If I were you, I would simply make this data completely dynamic
final class MenuOption {
private final String category; //MAIN_MENU, HOT_SELECTION
private final String message; //Exit, View Reservation By Host, etc.
public static MenuOption of(String key, String message) {
return new MenuOption(key, message);
}
}
This is very scalable, although it introduces the need to validate data where enums would statically prevent bad options, and possibly custom code where an enum would offer built-in support.
It can be improved with a "category" enum, which gives static access to menu lists, and a single place for asListString()
:
enum MenuCategory {
MAIN_MENU(
MenuOption.of("Exit"),
MenuOption.of("View Reservations By Host")
),
HOT_SELECTION(
MenuOption.of("Find All")
);
private final List<MenuOption> menuOptions;
MenuCategory(MenuOption... options) {
this.menuOptions = List.of(options); //unmodifiable
}
public List<String>asListString() {
return this.menuOptions.stream()
.map(MenuOption::getMessage)
.collect(Collectors.toList());
}
}
It should be clear that you can replace class MenuOption
with a bunch of enums implementing a common interface, which should change little to nothing in MenuCategory
. I wouldn't do that, but it's an option.