我可以在 Java 枚举上使用构建器模式吗

问题描述 投票:0回答:3

我正在重写一些代码,并且我已经决定了重新创建类的方式,因为有固定数量的工作表,我将它们创建为枚举。这是基于构建器模式与伸缩构造函数的可读性的决定。

代码获取一些 .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 期望我的枚举声明(顶部以逗号分隔的“枚举构造函数”列表)仅包含构造函数,而不包含其他方法。我可以将这些方法移至下面的“构建器”方法,而无需抱怨。

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

这是在枚举上实现构建器模式的唯一方法吗?它确实需要我运行一个单独的方法,这并不是太麻烦。不过,它确实感觉我正在打破模式(我想,如果这有效的话,也不是什么坏事。)

N.B 我已经仔细查看了周围是否有其他人在 SO 或网络上的其他地方问过这个问题。我发现的最接近的是关于枚举和工厂的问题,但这并不能完全回答我的问题。另外我知道这不完全是构建器模式,因为我没有一个单独的类来接受创建新枚举的 build() 方法。我想这是我最初设计中问题的根源,但我对 Java 还比较陌生。

那么有没有更好的方法在 Java 枚举上使用构建器模式?或者我所拥有的“足够接近”?

java enums builder
3个回答
30
投票

虽然它并不严格符合构建器模式,但简短的答案是肯定的。有点像。

缺少的部分是无法调用

.build()
来实例化枚举常量,因为 build() 无法使用
new
。但是您可以获得构建器模式的很多好处。让我们面对现实吧,你不能使用静态工厂方法,并且枚举常量的内联子类化很奇怪。

这是使用国家/地区枚举的示例。

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

如果您有构建常量的常用方法,您甚至可以将静态工厂方法放入构建器中。

所以它不完全是布洛赫的构建器,但非常接近。


7
投票

您可以使用实例块(通常不正确地称为“双括号初始值设定项”)来使用任意代码自定义构造:

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
所施加的限制,但仍然具有构建器/流畅模式的简洁性、便利性和灵活性。


1
投票

mySheet1, mySheet2
等是枚举常量,遵循第8.9.1

节中定义的JLS语法

枚举常量: 注释opt 标识符参数opt ClassBodyopt

因此,您可以在枚举常量后面添加参数列表(传递给构造函数的参数),但在声明枚举常量时不能调用枚举常量的方法。最多可以为其添加一个类体。

除此之外,使用构建器模式来构建枚举实例是有问题的,因为通常当您有大量实例(字段值的组合)时使用构建器模式,而不是用于少数实例的枚举概念。

© www.soinside.com 2019 - 2024. All rights reserved.