Enum con muchas propiedades booleanas

11

Actualmente estoy trabajando en una aplicación web en la que a menudo necesitamos condicionar cierta lógica del servidor en función de la página que se devolverá al usuario.

Cada página recibe un código de página de 4 letras, y estos códigos de página se enumeran actualmente en una clase como cadenas estáticas:

public class PageCodes {
    public static final String FOFP = "FOFP";
    public static final String FOMS = "FOMS";
    public static final String BKGD = "BKGD";
    public static final String ITCO = "ITCO";
    public static final String PURF = "PURF";
    // etc..
}

Y a menudo en el código vemos código como este ( 1er formulario ):

if (PageCode.PURF.equals(destinationPageCode) || PageCodes.ITCO.equals(destinationPageCode)) {
    // some code with no obvious intent
} 
if (PageCode.FOFP.equals(destinationPageCode) || PageCodes.FOMS.equals(destinationPageCode)) {
    // some other code with no obvious intent either
} 

Lo cual es horrible de leer porque no muestra qué propiedad común de estas páginas llevó al autor del código a reunirlas aquí. Tenemos que leer el código en la iframa para entender.

Solución actual

Estos ifmensajes se han simplificado en parte mediante listas de páginas declaradas por diferentes personas en diferentes clases. Esto hace que el código se vea como ( 2da forma ):

private static final List<String> pagesWithShoppingCart = Collections.unmodifiableList(Arrays.asList(PageCodes.ITCO, PageCodes.PURF));
private static final List<String> flightAvailabilityPages = Collections.unmodifiableList(Arrays.asList(PageCodes.FOMS, PageCodes.FOFP));

// later in the same class
if (pagesWithShoppingCart.contains(destinationPageCode)) {
    // some code with no obvious intent
} 
if (flightAvailabilityPages.contains(destinationPageCode)) {
    // some other code with no obvious intent either
} 

... que expresa la intención mucho mejor. Pero...

Problema actual

El problema aquí es que si agregamos una página, en teoría tendríamos que revisar toda la base de código para encontrar si necesitamos agregar nuestra página if()a una lista como esa.

Incluso si movimos todas esas listas a la PageCodesclase como constantes estáticas, aún necesita disciplina de los desarrolladores para verificar si su nueva página encaja en alguna de esas listas y agregarla en consecuencia.

Nueva solución

Mi solución fue crear una enumeración (porque hay una lista finita bien conocida de códigos de página) donde cada página contiene algunas propiedades que necesitamos establecer:

public enum Page {
    FOFP(true, false),
    FOMS(true, false),
    BKGD(false, false),
    PURF(false, true),
    ITCO(false, true),
    // and so on

    private final boolean isAvailabilityPage;
    private final boolean hasShoppingCart;

    PageCode(boolean isAvailabilityPage, boolean hasShoppingCart) {
        // field initialization
    }

    // getters
}

Entonces el código condicional ahora se ve así ( tercera forma ):

if (destinationPage.hasShoppingCart()) {
    // add some shopping-cart-related data to the response
}
if (destinationPage.isAvailabilityPage()) {
    // add some info related to flight availability
}

Lo cual es muy legible. Además, si alguien necesita agregar una página, se ve obligado a pensar en cada booleano y si esto es verdadero o falso para su nueva página.

Nuevo problema

Un problema que veo es que tal vez habrá 10 booleanos como este, lo que hace que el constructor sea realmente grande y puede ser difícil obtener la declaración correcta cuando agrega una página. ¿Alguien tiene una solución mejor?

Joffrey
fuente

Respuestas:

13

Puede llevar la idea un paso más allá y definir una enumeración para las características de su página en lugar de utilizar booleanos.

Esto facilita agregar / eliminar una función a una página y hace que las definiciones de la página sean legibles al instante, incluso cuando hay 30-40 funciones potenciales.

public enum PageFeature {
    AVAIL_PAGE,
    SHOPPING_CART;
}

public enum Page {
    FOFP(AVAIL_PAGE),
    FOMS(AVAIL_PAGE),
    BKGD(),
    PURF(SHOPPING_CART, AVAIL_PAGE),

    private final EnumSet<PageFeature> features;

    PageCode(PageFeature ... features) {
       this.features = EnumSet.copyOf(Arrays.asList(features));
    }

    public boolean hasFeature(PageFeature feature) {
       return features.contains(feature);
    }
 }
biziclop
fuente
Pensé en esto, pero aquí, el desarrollador no está obligado a responder a todas las preguntas "¿es una página de disponibilidad?", "¿Tiene un carrito de compras?" etc. Cuando hay muchos de ellos, fácilmente olvidas uno.
Joffrey
55
@Joffrey Cuando lo piensas, tener diez booleanos tampoco los obliga a dar una respuesta, al menos no una respuesta en la que piensan. El escenario más probable es que simplemente copien la definición de otra página o simplemente dejen que el IDE complete todos los parámetros falsey luego modifiquen uno o dos (probablemente el incorrecto, ya que es muy difícil hacer un seguimiento). No hay una protección perfecta contra los estúpidos, y llega un punto en el que debes confiar en tus desarrolladores para hacer lo correcto.
biziclop
Bien, no lo pensé de esta manera :) Y no creo que el pequeño "infalible" adicional dado por el booleano valga la degradación de la lisibilidad, por lo que probablemente iré con la solución vararg. Gracias por tu perspicacia!
Joffrey
1
Buena solución, votada. Aunque la parte de mí que codificó en 6502 quiere meter todo en pedazos. :-)
user949300
@ user949300 la primera solución tipo vararg en la que pensé era en realidad máscaras de bits :) pero un verdadero vararg con un tipo de enumeración es más limpio
Joffrey