Tengo dos enumeraciones:
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()); } }Ambas enumeraciones comparten el mismo campo.
private final String message;el mismo captador
public String getMessage() { return message; }Y lo mismo que el método ListString()
public static List<String> asListString() { return Arrays.stream(MainMenuOptions.values()) .map(MainMenuOptions::getMessage) .collect(Collectors.toList()); }Espero tener más enumeraciones con los mismos campos y métodos, y parece una tontería escribir lo mismo una y otra vez para cada uno.
El sabor que esperaba que el código pudiera tener es algo como esto:
public class Utils { public static List<String> enumAsListString(Enum e) { return e.values().stream.map(e::getMessage).collect(Collectors.toList()); } }Este es probablemente uno de los casos en los que debe elegir uno entre SECO y usar enumeraciones.
Las enumeraciones no van muy lejos en lo que respecta a la reutilización de código, al menos en Java; y la razón principal de esto es que los beneficios principales del uso de enumeraciones se obtienen en código estático; me refiero a estático como en "no dinámico"/"tiempo de ejecución", en lugar de static :). Aunque puede "reducir" la duplicación de código, difícilmente puede hacer mucho de eso sin introducir dependencia (sí, eso se aplica a agregar una API/interfaz común, extrayendo la implementación de asListString a una clase de utilidad). Y eso sigue siendo una compensación indeseable.
Además, si debe usar una enumeración (por razones tales como soporte incorporado para serialización, mapeo de base de datos, enlace JSON o, bueno, porque es enumeración de datos, etc.), no tiene más remedio que duplicar declaraciones de métodos a un medida, incluso si puede compartir la implementación: los métodos estáticos simplemente no se pueden heredar, y los métodos de interfaz (de los cuales getMessage sería uno) necesitarán una implementación en todas partes. Me refiero a que esta forma de ser "DRY" tendrá muchas formas de ser poco elegante.
Si yo fuera usted, simplemente haría que estos datos fueran completamente dinámicos.
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); } }Esto es muy escalable, aunque presenta la necesidad de validar datos donde las enumeraciones evitarían de forma estática malas opciones, y posiblemente código personalizado donde una enumeración ofrecería soporte integrado.
Se puede mejorar con una enumeración de "categoría", que brinda acceso estático a las listas de menú y un solo lugar para 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()); } } Debería quedar claro que puede reemplazar class MenuOption con un montón de enumeraciones que implementan una interfaz común, que debería cambiar poco o nada en MenuCategory . Yo no haría eso, pero es una opción.
Puedes SECARLO un poco.
Utils.java
import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public interface Utils<T> { public String getMessage(); public static <T extends Utils<T>> List<String> asListString(Class<T> clazz) { return Arrays.stream(clazz.getEnumConstants()) .map(T::getMessage) .collect(Collectors.toList()); } }HostSelectionMethodOptions.java
public enum HostSelectionMethodOptions implements Utils<HostSelectionMethodOptions> { FIND_ALL("Find all"), FIND_BY_LASTNAME_PREFIX("Find by last name prefix"), FIND_BY_CITY_STATE("Find by city & state"); private final String message; HostSelectionMethodOptions(String message) { this.message = message; } public String getMessage() { return message; } } Entonces solo haz esto: Utils.asListString(HostSelectionMethodOptions.class);
Básicamente tuve la misma idea que davidalayachew.
Una enumeración puede implementar una interfaz. Entonces, si crea un asListString común que acepta un tipo de enum , podría obtener el resultado deseado.
Primero, cree una interfaz de Options y deje que ambos enum la implementen:
interface Options { String getMessage(); } enum HostSelectionMethodOptions implements Options { ... } enum MainMenuOptions implements Options { ... }Ahora crea un método como este:
public static <T extends Enum<T> & Options> List<String> asListString(Class<T> type) { return Arrays.stream(type.getEnumConstants()) .map(T::getMessage) .collect(Collectors.toList()); } El método declara un argumento de tipo: <T extends Enum<T> & Options> . Aquí, T es un tipo de intersección , por lo que extiende tanto Enum como la interfaz de Options . Puedes llamarlo así:
asListString(MainMenuOptions.class);