2013-03-14 12 views
13

Sto riscrivendo del codice e ho deciso il modo di ricreare la classe, dato che ci sono un numero fisso di fogli, li sto creando come enumerazioni . Questa è una decisione basata sulla leggibilità di un builder di build rispetto a un costruttore telescopico.Posso usare il modello di builder su Java Enum

Il codice Sto prendendo alcuni file .xls, aggiungo intestazioni (e ne lessi alcuni da altri file .xls) e forse alcuni sotto-fogli. Unisce quindi una varietà di questi fogli insieme in un modo specifico per creare schede su una cartella di lavoro principale di Excel. Il mio problema è che alcune schede della cartella di lavoro richiedono numeri diversi di fogli sono argomenti. Sto provando ad applicare il modello di builder. Questo è il tipo di codice che sto cercando di scrivere:

public enum workBookSheet { 
    mySheet1("Name1","mainSheet1.xls",true,1).addSubSheet("pathToSubSheet1.xls"), 
    mySheet2("Name2","mainSheet2.xls",true,2).addHeaderSheet("pathToHeaders.xls").addSubsheet("pathtoSubSheet2.xls"); 

    private String tabName; 
    private String mainSheetName; 
    private Boolean available; 
    private Integer order; 
    private String subSheetName; 
    private String headerSheetName; 

    private workBookSheet(String tabName, String mainSheetName, Boolean available, Integer order){ 
     this.tabName = tabName; 
     this.mainSheetName = mainSheetName; 
     this.available = available; 
     this.order = order; 
    } 
    public workBookSheet addSubSheet(String subSheetName){ 
     this.subSheetName = subSheetName; 
     return this; 
    } 
    public workBookSheet addHeaderSheet(String headerSheetName){ 
     this.headerSheetName = headerSheetName; 
     return this; 
    } 

} 

L'errore che Java sta dando a me sembra dire che Java si aspetta che la mia dichiarazione enum (virgole elenco dei 'costruttori enum' delimitato in alto) avere solo il costruttore in esso, e non metodi aggiuntivi. Posso spostare questi metodi in un metodo "costruttore" di seguito, senza lamentarsi.

public void buildSheets(){ 
    mySheet1.addSubSheet("pathToSubSheet1.xls"); 
    mySheet2.addHeaderSheet("pathToHeaders.xls").addSubSheet("pathtoSubSheet2.xls"); 
} 

È questo l'unico modo per realizzare un builder su un enum? Mi richiede di eseguire un metodo separato, che non è troppo fastidioso. Mi sembra che sto rompendo il modello (suppongo, non così male se funziona.)

NB Ho avuto una buona occhiata in giro per vedere se qualcun altro ha posto questa domanda, su SO o altrove sul web. Il più vicino che ho trovato era una domanda qui su Enums and Factories, ma questo non risponde alla mia domanda. Inoltre sono consapevole che questo non è proprio il modello di builder, in quanto non ho una classe separata che accetta un metodo build() che crea una nuova enumerazione. Immagino che questa sia la radice del problema nella mia progettazione iniziale, ma sono relativamente nuovo in Java.

Quindi, c'è un modo migliore per utilizzare un modello di build su un enum Java? O è quello che ho 'abbastanza vicino'?

risposta

14

Sebbene non sia strettamente conforme al modello di builder, la risposta breve è sì. Una specie di.

Il pezzo mancante non è in grado di chiamare .build() per creare un'istanza della costante enum, perché build() non può utilizzare new. Ma puoi ottenere alcuni dei vantaggi del modello di builder.E ammettiamolo, non è possibile utilizzare i metodi di factory static e la sottoclasse inline delle costanti enum è strana.

Ecco un esempio utilizzando l'enumerazione Paese.

package app; 

import org.apache.commons.lang.StringUtils; 
import javax.annotation.Nullable; 
import java.util.EnumSet; 
import java.util.Set; 
import static app.Language.*; 
import static com.google.common.base.Preconditions.*; 

enum Language { 
    ITALIAN, 
    ENGLISH, 
    MALTESE 
} 

public enum Country { 

    ITALY(new Builder(1, "Italy").addLanguage(ITALIAN)), 
    MALTA(new Builder(2, "Malta").addLanguages(MALTESE, ENGLISH, ITALIAN).setPopulation(450_000)); 

    final private int id; 
    final private String name; 
    final private Integer population; 
    final private Set<Language> languages; 

    private static class Builder { 

     private int id; 
     private String name; 
     private Integer population; 
     private Set<Language> languages = EnumSet.noneOf(Language.class); 

     public Builder(int id, String name) { 
      checkArgument(!StringUtils.isBlank(name)); 

      this.id = id; 
      this.name = name; 
     } 

     public Builder setPopulation(int population) { 
      checkArgument(population > 0); 

      this.population = population; 
      return this; 
     } 

     public Builder addLanguage(Language language) { 
      checkNotNull(language); 

      this.languages.add(language); 
      return this; 
     } 

     public Builder addLanguages(Language... language) { 
      checkNotNull(language); 

      this.languages.addAll(languages); 
      return this; 
     } 
    } 

    private Country(Builder builder) { 

     this.id = builder.id; 
     this.name = builder.name; 
     this.population = builder.population; 
     this.languages = builder.languages; 

     checkState(!this.languages.isEmpty()); 
    } 

    public int getId() { 
     return id; 
    } 

    public String getName() { 
     return name; 
    } 

    @Nullable 
    public Integer getPopulation() { 
     return population; 
    } 

    public Set<Language> getLanguages() { 
     return languages; 
    } 
} 

Si può anche mettere metodi factory statici nel generatore di se avete modi comuni per costruire una costante.

Quindi non è proprio il costruttore di Bloch, ma è piuttosto vicino.

+0

È qualcosa che hai usato in precedenza o è questo nuovo codice che hai scritto come esempio? – Pureferret

+3

Un po 'di entrambi. Volevo usare un costruttore, ho trovato alcune risposte ma non mi piacevano e ho pensato di farlo in questo modo. Poi ho pensato di condividere la mia soluzione in questa domanda nel caso in cui aiutasse qualcuno. –

+1

Grazie mille! Ciò ha reso la mia lezione di enumerazione molto più leggibile. –

1

mySheet1, mySheet2, ecc, sono costanti enum che segue la sintassi JLS definito nella sezione 8.9.1

EnumConstant: Annotationsopt Identifier Argumentsopt ClassBodyopt

Quindi, è possibile seguire l'enum costante da una lista di argomenti (i parametri da passare al costruttore), ma si non è possibile chiamare un metodo sulla costante enum mentre lo si dichiara. Al massimo puoi aggiungere un corpo di classe per questo.

Oltre a questo, l'utilizzo di builder per costruire Enumera istanze è discutibile, come in generale la builder viene utilizzato quando si dispone di un gran numero di istanze (combinazioni di valori di campo), in contrasto con il concetto di enumerazioni utilizzato per un poche istanze.

+0

Grazie per la tua risposta dcernahoschi, soprattutto per avermi indicato le Definizioni. In realtà, in realtà ho 20-25 istanze enum in gioco, ognuna con una combinazione leggermente diversa di intestazioni, sottotitoli, ecc. Quanti sono abbastanza "grandi" da giustificare un costruttore, quanto pochi sono pochi per un enum? Con più sviluppo, questo potrebbe cambiare (le intestazioni hard codded in seguito nella classe potrebbero essere convertite in header.xls, ecc.) Quindi questo schema mi sembra adatto. – Pureferret

4

È possibile utilizzare i blocchi di istanza (spesso in modo non corretto chiamati "initializers doppia trave perimetrale") per personalizzare costruzione con codice arbitrario:

public enum workBookSheet { 

    mySheet1("Name1", "mainSheet1.xls", true, 1) {{ 
     addSubSheet("pathToSubSheet1.xls"); 
    }}, 
    mySheet2("Name2", "mainSheet2.xls", true, 2) {{ 
     // you can use the fluent interface: 
     addHeaderSheet("pathToHeaders.xls").addSubSheet("pathtoSubSheet2.xls"); 
     // but I would prefer coding separate statements: 
     addHeaderSheet("pathToHeaders.xls"); 
     addSubSheet("pathtoSubSheet2.xls"); 
    }}; 

    // rest of your class the same... 
} 

Utilizzando questa sintassi permette di aggirare le limitazioni imposte da un enum ma hanno ancora la brevità, la convenienza e la flessibilità di un modello costruttore/fluente.

+0

Questo è un trucco molto intelligente! Le mie uniche domande sono: Devo modificare i metodi, e posso scrivere il contenuto del blocco di istanze come 'this.methodName (...);', come preferisco essere esplicito quando si scrivono i metodi. – Pureferret

+1

Non * è necessario * per modificare i metodi, tuttavia non mi preoccuperei dello stile fluente (restituendo 'this') e renderei quindi' private', a meno che * non * sia necessario renderli visibili all'esterno della classe. Si può scrivere "questo", ma è ridondante e non raccomandato da un punto di vista dello stile, specialmente perché i metodi di 'this' * mai * devono essere qualificati; solo i campi richiedono la qualifica e solo quando si scontrano con i nomi dei parametri e non ci sono parametri per i blocchi di istanze. – Bohemian

+0

Bohemian, ho provato a usare i blocchi di istanza, ma la classe in cui si trova l'enumerazione è statica, e inserendoli in posizione significa errore. Li sto lasciando come sono per ora, ma se trovo il tempo cercherò di aggirare il problema. Per ora, se sai come dovrebbero essere i metodi, potresti inserirli nella risposta? – Pureferret