问题
Givethis Dr Dobbs article,特别是Builder Pattern,我们如何处理子类化Builder的情况?在我们想要子类化以添加GMO标签的示例的简化版本中,一个天真的实现将是:
public class NutritionFacts {
private final int calories;
public static class Builder {
private int calories = 0;
public Builder() {}
public Builder calories(int val) { calories = val; return this; }
public NutritionFacts build() { return new NutritionFacts(this); }
}
protected NutritionFacts(Builder builder) {
calories = builder.calories;
}
}
子类:
public class GMOFacts extends NutritionFacts {
private final boolean hasGMO;
public static class Builder extends NutritionFacts.Builder {
private boolean hasGMO = false;
public Builder() {}
public Builder GMO(boolean val) { hasGMO = val; return this; }
public GMOFacts build() { return new GMOFacts(this); }
}
protected GMOFacts(Builder builder) {
super(builder);
hasGMO = builder.hasGMO;
}
}
现在,我们可以编写如下代码:
GMOFacts.Builder b = new GMOFacts.Builder();
b.GMO(true).calories(100);
但是,如果我们得到错误的订单,那一切都会失败:
GMOFacts.Builder b = new GMOFacts.Builder();
b.calories(100).GMO(true);
问题当然是5824235783返回aNutritionFacts.Builder
,而不是aGMOFacts.Builder
,那么我们如何解决这个问题,还是有更好的模式使用?
注意:this answer to a similar question提供我上面的课程;我的问题是关于确保构建器调用的顺序正确的问题。
#1 热门回答(136 赞)
你可以使用泛型来解决它。我认为这被称为the"Curiously recurring generic patterns"
使基类构建器方法的返回类型成为通用参数。
public class NutritionFacts {
private final int calories;
public static class Builder<T extends Builder<T>> {
private int calories = 0;
public Builder() {}
public T calories(int val) {
calories = val;
return (T) this;
}
public NutritionFacts build() { return new NutritionFacts(this); }
}
protected NutritionFacts(Builder<?> builder) {
calories = builder.calories;
}
}
现在使用派生类构建器作为泛型参数来实例化基础构建器。
public class GMOFacts extends NutritionFacts {
private final boolean hasGMO;
public static class Builder extends NutritionFacts.Builder<Builder> {
private boolean hasGMO = false;
public Builder() {}
public Builder GMO(boolean val) {
hasGMO = val;
return this;
}
public GMOFacts build() { return new GMOFacts(this); }
}
protected GMOFacts(Builder builder) {
super(builder);
hasGMO = builder.hasGMO;
}
}
#2 热门回答(29 赞)
只是为了记录,摆脱了
未经检查或不安全的操作警告
对于return (T) this;
声明为@dimadima和@Thomas N.谈论,以下解决方案适用于某些情况。
Makeabstract
声明泛型类型的构建器(本例中为T extends Builder
)并声明如下的protected abstract T getThis()
abstract方法:
public abstract static class Builder<T extends Builder<T>> {
private int calories = 0;
public Builder() {}
/**The solution for the unchecked cast warning. */
public abstract T getThis();
public T calories(int val) {
calories = val;
// no cast needed
return getThis();
}
public NutritionFacts build() { return new NutritionFacts(this); }
}
更多详细信息,请参阅http://www.angelikalanger.com/GenericsFAQ/FAQSections/ProgrammingIdioms.html#FAQ205。
#3 热门回答(17 赞)
基于a blog post,这种方法要求所有非叶类都是抽象的,并且所有叶类必须是最终的。
public abstract class TopLevel {
protected int foo;
protected TopLevel() {
}
protected static abstract class Builder
<T extends TopLevel, B extends Builder<T, B>> {
protected T object;
protected B thisObject;
protected abstract T createObject();
protected abstract B thisObject();
public Builder() {
object = createObject();
thisObject = thisObject();
}
public B foo(int foo) {
object.foo = foo;
return thisObject;
}
public T build() {
return object;
}
}
}
然后,你有一些扩展此类及其构建器的中间类,以及你需要的更多内容:
public abstract class SecondLevel extends TopLevel {
protected int bar;
protected static abstract class Builder
<T extends SecondLevel, B extends Builder<T, B>> extends TopLevel.Builder<T, B> {
public B bar(int bar) {
object.bar = bar;
return thisObject;
}
}
}
最后,一个具体的叶类,可以按任何顺序调用任何父类的所有构建器方法:
public final class LeafClass extends SecondLevel {
private int baz;
public static final class Builder extends SecondLevel.Builder<LeafClass,Builder> {
protected LeafClass createObject() {
return new LeafClass();
}
protected Builder thisObject() {
return this;
}
public Builder baz(int baz) {
object.baz = baz;
return thisObject;
}
}
}
然后,你可以从层次结构中的任何类调用任何顺序的方法:
public class Demo {
LeafClass leaf = new LeafClass.Builder().baz(2).foo(1).bar(3).build();
}