提问人:AncientSwordRage 提问时间:3/14/2013 更新时间:5/30/2013 访问量:12822
我可以在 Java 枚举上使用构建器模式吗
Can I use the builder pattern on a Java Enum
问:
我正在重写一些代码,并且我已经决定了重新创建类的方法,因为有固定数量的工作表,我将它们创建为枚举。这是基于构建器模式与伸缩构造器的可读性的决定。
我正在的代码抓取了一些.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");
}
这是在枚举上实现构建器模式的唯一方法吗?它确实需要我运行一个单独的方法,这并不麻烦。不过,它确实觉得我正在打破这种模式(我想,如果这可行,这并不是一件坏事。
注意:我仔细环顾四周,看看是否有其他人在 SO 或网络上的其他地方问过这个问题。我发现的最接近的是关于枚举和工厂的问题,但这并不能完全回答我的问题。我也知道这并不完全是构建器模式,因为我没有一个单独的类,然后接受创建一个新枚举的 build() 方法。我想这是我最初设计中问题的根源,但我对 Java 相对较新。
那么,有没有更好的方法可以在 Java 枚举上使用构建器模式呢?还是我所拥有的“足够接近”?
答:
mySheet1, mySheet2
等是枚举常量,遵循第 8.9.1 节中定义的 JLS 语法
枚举常量: 注解opt 标识符 参数opt 类体opt
因此,您可以在枚举常量后面加上参数列表(要传递给构造函数的参数),但不能在声明枚举常量时调用该方法。您最多可以为其添加一个类主体。
除此之外,您使用构建器模式来构建枚举实例是有问题的,因为通常,当您拥有大量实例(字段值的组合)时,会使用构建器模式,而使用用于少数实例的枚举概念。
评论
您可以使用实例块(通常错误地称为“双大括号初始值设定项”)来自定义具有任意代码的构造:
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
评论
this.methodName(...);
this
private
this.
this
Error:(8, 14) java: non-static method id(java.lang.String) cannot be referenced from a static context
虽然它并不严格符合构建器模式,但简短的回答是肯定的。算是吧。
缺少的部分是无法调用来实例化枚举常量,因为 build() 不能使用 .但是,您可以获得构建器模式的很多好处。让我们面对现实吧,你不能使用静态工厂方法,枚举常量的内联子类化很奇怪。.build()
new
下面是使用 Country 枚举的示例。
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;
}
}
如果您有构建常量的常用方法,您甚至可以将静态工厂方法放在构建器中。
所以它不完全是布洛赫的建造者,但它非常接近。
评论