Enum mit vielen booleschen Eigenschaften

11

Ich arbeite derzeit an einer Webanwendung, bei der wir häufig eine Serverlogik basierend auf der Seite konditionieren müssen, die an den Benutzer zurückgegeben wird.

Jede Seite erhält einen 4-Buchstaben-Seitencode. Diese Seitencodes werden derzeit in einer Klasse als statische Zeichenfolgen aufgeführt:

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..
}

Und oft sehen wir im Code Code wie diesen ( 1. Form ):

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
} 

Das ist schrecklich zu lesen, weil es nicht zeigt, welche gemeinsamen Eigenschaften dieser Seiten den Autor des Codes veranlasst haben, sie hier zusammenzustellen. Wir müssen den Code in der ifVerzweigung lesen, um zu verstehen.

Aktuelle Lösung

Diese ifwurden teilweise vereinfacht, indem Listen von Seiten verwendet wurden, die von verschiedenen Personen in verschiedenen Klassen deklariert wurden. Dadurch sieht der Code wie folgt aus ( 2. Form ):

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
} 

... was die Absicht viel besser ausdrückt. Aber...

Aktuelles Problem

Das Problem hierbei ist, dass wir, wenn wir eine Seite hinzufügen, theoretisch die gesamte Codebasis durchgehen müssten, um herauszufinden, ob wir unsere Seite einer if()oder einer solchen Liste hinzufügen müssen .

Selbst wenn wir alle diese Listen PageCodesals statische Konstanten in die Klasse verschoben haben, müssen die Entwickler diszipliniert prüfen, ob ihre neue Seite in eine dieser Listen passt, und sie entsprechend hinzufügen.

Neue Lösung

Meine Lösung bestand darin, eine Aufzählung zu erstellen (da es eine endliche bekannte Liste von Seitencodes gibt), in der jede Seite einige Eigenschaften enthält, die wir festlegen müssen:

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
}

Dann sieht der bedingte Code jetzt so aus ( 3. Form ):

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

Welches ist sehr gut lesbar. Wenn jemand Bedarf zusätzlich eine Seite hinzuzufügen, er / sie ist gezwungen , über jedes boolean zu denken und ob diese wahr oder falsch ist für seine neue Seite.

Neues Problem

Ein Problem, das ich sehe, ist, dass es vielleicht 10 Boolesche Werte wie diesen gibt, was den Konstruktor wirklich groß macht und es schwierig sein kann, die Deklaration richtig zu machen, wenn Sie eine Seite hinzufügen. Hat jemand eine bessere Lösung?

Joffrey
quelle

Antworten:

13

Sie können die Idee noch einen Schritt weiter gehen und eine Aufzählung für Ihre Seitenfunktionen definieren, anstatt Boolesche Werte zu verwenden.

Dies erleichtert das Hinzufügen / Entfernen einer Funktion zu einer Seite und macht die Seitendefinitionen sofort lesbar, selbst wenn 30 bis 40 potenzielle Funktionen vorhanden sind.

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
quelle
Ich habe darüber nachgedacht, aber hier ist der Entwickler nicht gezwungen, alle Fragen zu beantworten: "Ist es eine verfügbare Seite?", "Hat es einen Einkaufswagen?" usw. Wenn es viele davon gibt, vergisst man leicht eine.
Joffrey
5
@Joffrey Wenn Sie darüber nachdenken, zwingen zehn Boolesche Werte sie auch nicht dazu, eine Antwort zu geben, zumindest keine Antwort, über die sie nachdenken. Das wahrscheinlichste Szenario ist, dass sie einfach die Definition einer anderen Seite kopieren oder einfach die IDE alle Parameter mit füllen lassen falseund dann einen oder zwei ändern (wahrscheinlich den falschen, da es so schwer ist, den Überblick zu behalten). Es gibt keinen perfekten Schutz vor Dummheit, und irgendwann müssen Sie Ihren Entwicklern vertrauen, dass sie das Richtige tun.
Biziclop
Richtig, ich habe nicht so darüber nachgedacht :) Und ich denke nicht, dass der kleine zusätzliche "narrensichere" Wert, den der Boolesche Wert gibt, die Verschlechterung der Lesbarkeit wert ist, also werde ich mich wahrscheinlich für die Vararg-Lösung entscheiden. Vielen Dank für Ihren Einblick!
Joffrey
1
Gute Lösung, positiv bewertet. Obwohl der Teil von mir, der auf 6502s codiert hat, alles in Stücke packen will. :-)
user949300
@ user949300 die erste vararg-ähnliche Lösung, über die ich nachdachte, waren tatsächlich Bitmasken :) aber ein echtes vararg mit einem Aufzählungstyp ist sauberer
Joffrey