Могу ли я использовать шаблон builder в перечислении Java


Я переписываю некоторый код, и я решил, как воссоздать класс, поскольку есть фиксированное количество листов, я создаю их как перечисления. Это решение основано на удобочитаемости паттерна строителя против телескопического конструктора.

Код, который я использую, захватывает некоторые некоторые .xls файлы, добавляет заголовки (и читает некоторые из других .файлы xls) и, возможно, некоторые вложенные листы. Затем он объединяет различные эти листы вместе определенным образом, чтобы сделать вкладки на главной книге excel. Моя проблема в том, то, что некоторые вкладки книги занимают разное количество листов, является аргументом. Я пытаюсь применить шаблон строителя. Вот такой код я пытаюсь написать:

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

}

Ошибка, которую дает мне java, похоже, говорит о том, что Java ожидает, что мое объявление enum (разделенный запятыми список "конструкторов enum" вверху) будет содержать только конструктор, а не дополнительные методы. Я могу перенести эти методы в метод "строителя" ниже, без жалоб.

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

Является ли это единственный способ реализовать шаблон компоновщика на перечислении? Это требует от меня запуска отдельного метода, что не слишком хлопотно. Это действительно похоже на то, что я нарушаю шаблон, хотя (я думаю, не такая уж плохая вещь, если это работает.)

N. B Я внимательно осмотрелся, чтобы увидеть, задавал ли кто-нибудь еще этот вопрос, на SO или где-нибудь в интернете. Ближе всего я нашел вопрос о перечислениях и фабриках, но это не совсем ответ на мой вопрос. Кроме того, я знаю, что это не совсем строитель pattern, поскольку у меня нет отдельного класса, который затем принимает метод build (), создающий новое перечисление. Я предполагаю, что это корень проблемы в моем первоначальном дизайне, но я относительно новичок в Java.

Итак, есть ли лучший способ использовать шаблон компоновщика в перечислении Java? Или то, что у меня есть, "достаточно близко"?

3 15

3 ответа:

Хотя это не совсем соответствует шаблону строителя, короткий ответ-да. Что-то вроде того.

Недостающая часть не может вызвать .build() для создания экземпляра константы перечисления, потому что build() не может использовать new. Но вы можете получить довольно много преимуществ шаблона builder. И давайте посмотрим правде в глаза, вы не можете использовать статические методы фабрики, и встроенное подклассовое перечисление констант странно.

Вот пример использования перечисления стран.

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

Можно даже поставить статические фабричные методы в конструкторе, если у вас есть общие способы построения константы.

Так что это не совсем строитель Блоха, но довольно близко.

Вы можете использовать блоки экземпляров (часто неправильно называемые "инициализаторами двойной скобки") для настройки конструкции с произвольным кодом:

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

Использование этого синтаксиса позволяет обойти ограничения, налагаемые enum, но при этом сохраняет краткость, удобство и гибкость шаблона builder/fluent.

mySheet1, mySheet2, и т.д. являются перечисленными константами, которые следуют синтаксису JLS, определенному в разделе 8.9.1

EnumConstant: Annotationsopt Идентификатор Argumentsopt ClassBodyopt

Таким образом, вы можете следовать за константой перечисления по списку аргументов (параметры для передачи конструктору), но вы не можете вызвать метод на константе перечисления при ее объявлении. Самое большее, вы можете добавить тело класса для него.

Кроме того, вы используете шаблон builder для построения экземпляров enum сомнительно, так как в общем случае шаблон builder используется при наличии большого количества экземпляров (комбинаций значений полей) в отличие от концепции перечислений, используемой для нескольких экземпляров.