子类化Java Builder类的设计模式
给出this Dr Dobbs article,特别是构建器模式,我们如何处理子类化构建器的情况?举一个简化版的例子,我们希望子类化以添加转基因标签,一个简单的实现是:
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);
问题当然是NutritionFacts.Builder
返回的是NutritionFacts.Builder
,而不是GMOFacts.Builder
,那么我们如何解决这个问题,或者是否有更好的模式可以使用
注:this answer to a similar question提供了我上面的类;我的问题是关于确保生成器调用的顺序正确的问题
# 1 楼答案
还有另一种根据
Builder
模式创建类的方法,该模式符合“优先组合而非继承”定义父类
Builder
将继承的接口:NutritionFacts
的实现几乎相同(除了Builder
实现“FactsBuilder”接口外):子类的
Builder
应该扩展相同的接口(不同的通用实现除外):请注意,
NutritionFacts.Builder
是GMOFacts.Builder
(称为baseBuilder
)内的一个字段。从FactsBuilder
接口实现的方法调用baseBuilder
同名的方法:在:
GMOFacts(Builder builder)
的构造函数中也有很大的变化。构造函数中对父类构造函数的第一次调用应传递适当的^{GMOFacts
类的完整实现:# 2 楼答案
为了记录在案,为了摆脱
对于@dimadima和@Thomas N.talk的
return (T) this;
语句,以下解决方案适用于某些情况使
abstract
成为声明泛型类型的生成器(本例中为T extends Builder
),并声明protected abstract T getThis()
抽象方法,如下所示:有关更多详细信息,请参阅http://www.angelikalanger.com/GenericsFAQ/FAQSections/ProgrammingIdioms.html#FAQ205
# 3 楼答案
基于a blog post,这种方法要求所有非叶类都是抽象的,所有叶类都必须是最终的
然后,您就有了一些中间类,它扩展了这个类及其构建器,并且可以根据需要扩展更多:
最后是一个具体的叶类,它可以按任何顺序调用任何父类上的所有生成器方法:
然后,可以从层次结构中的任何类中按任意顺序调用这些方法:
# 4 楼答案
多生成器继承的完整3级示例如下所示:
(对于为构建器提供复制构造函数的版本,请参见下面的第二个示例)
第一级-父级(可能是抽象的)
二级
三级
还有一个用法示例
稍长一点的版本,为构建者提供了一个复制构造函数:
第一级-父级(可能是抽象的)
二级
三级
还有一个用法示例
# 5 楼答案
还可以重写
calories()
方法,让它返回扩展生成器。之所以编译,是因为Java支持covariant return types# 6 楼答案
你可以用泛型来解决它。我想这叫做"Curiously recurring generic patterns"
将基类生成器方法的返回类型设为泛型参数
现在用派生类生成器作为泛型参数来实例化基本生成器