子类化Java Builder类

给这个Dobbs博士的文章 ,特别是Builder模式,我们如何处理一个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); 

问题当然是, NutritionFacts.Builder返回一个NutritionFacts.Builder ,而不是一个GMOFacts.Builder ,那么我们如何解决这个问题,还是有更好的模式使用?

注意: 对类似问题的回答提供了我上面的类; 我的问题是关于确保构build器调用按正确顺序的问题。

你可以用generics来解决它。 我认为这被称为“奇怪的反复出现的通用模式”

使基类构build器方法的返回types成为generics参数。

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

现在使用派生类生成器作为generics参数实例化基本生成器。

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

只是为了logging,摆脱了

unchecked or unsafe operations警告

为了return (T) this; 陈述为@dimadima和@Thomas N.谈论,下面的解决scheme在某些情况下适用。

abstract声明genericstypes的protected abstract T getThis() T extends Builder在这种情况下T extends Builder )并声明protected abstract T getThis()抽象方法如下:

 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

基于博客文章 ,这种方法要求所有的非叶类是抽象的,所有的叶类必须是最终的。

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

然后,你有一些中级的课程来扩展这个class级和它的build设者,还有更多你需要的:

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

最后,一个具体的叶子类可以以任何顺序在父母的任何一个上调用所有的构build器方法:

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

你也可以覆盖calories()方法,并让它返回扩展的构build器。 这个编译是因为Java支持协变返回types 。

 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 Builder calories(int val) { super.calories(val); return this; } public GMOFacts build() { return new GMOFacts(this); } } [...] } 

如果你不想用尖括号或三angular刺出你的眼睛,或者感觉不到你……呃……我的意思是…… 咳嗽 ……你的团队的其他成员很快就会好奇地理解反复出现的generics模式,你可以这样做:

 public class TestInheritanceBuilder { public static void main(String[] args) { SubType.Builder builder = new SubType.Builder(); builder.withFoo("FOO").withBar("BAR").withBaz("BAZ"); SubType st = builder.build(); System.out.println(st.toString()); builder.withFoo("BOOM!").withBar("not getting here").withBaz("or here"); } } 

支持

 public class SubType extends ParentType { String baz; protected SubType() {} public static class Builder extends ParentType.Builder { private SubType object = new SubType(); public Builder withBaz(String baz) { getObject().baz = baz; return this; } public Builder withBar(String bar) { super.withBar(bar); return this; } public Builder withFoo(String foo) { super.withFoo(foo); return this; } public SubType build() { // or clone or copy constructor if you want to stamp out multiple instances... SubType tmp = getObject(); setObject(new SubType()); return tmp; } protected SubType getObject() { return object; } private void setObject(SubType object) { this.object = object; } } public String toString() { return "SubType2{" + "baz='" + baz + '\'' + "} " + super.toString(); } } 

和父types:

 public class ParentType { String foo; String bar; protected ParentType() {} public static class Builder { private ParentType object = new ParentType(); public ParentType object() { return getObject(); } public Builder withFoo(String foo) { if (!"foo".equalsIgnoreCase(foo)) throw new IllegalArgumentException(); getObject().foo = foo; return this; } public Builder withBar(String bar) { getObject().bar = bar; return this; } protected ParentType getObject() { return object; } private void setObject(ParentType object) { this.object = object; } public ParentType build() { // or clone or copy constructor if you want to stamp out multiple instances... ParentType tmp = getObject(); setObject(new ParentType()); return tmp; } } public String toString() { return "ParentType2{" + "foo='" + foo + '\'' + ", bar='" + bar + '\'' + '}'; } } 

关键点:

  • 将对象封装在构build器中,以便inheritance可以防止在父types中保留的对象上设置字段
  • 调用超级确保添加到超级types构build器方法的逻辑(如果有的话)保留在子types中。
  • 下面是在父类(es)虚假的对象创build…但请参阅下面的方法来清理
  • 一眼就可以更容易理解,而且没有详细的构造函数传递属性。
  • 如果你有多个线程访问你的构build器对象…我想我很高兴我不是你:)。

编辑:

我find了一个围绕虚假对象创build的方法。 首先将其添加到每个生成器:

 private Class whoAmI() { return new Object(){}.getClass().getEnclosingMethod().getDeclaringClass(); } 

然后在每个构build器的构造函数中:

  if (whoAmI() == this.getClass()) { this.obj = new ObjectToBuild(); } 

成本是new Object(){}匿名内部类的一个额外的类文件

还有另外一种方法可以根据Builder模式创build类,它符合“优先组合inheritance”。

定义一个接口,父类Builder将inheritance:

 public interface FactsBuilder<T> { public T calories(int val); } 

NutritionFacts的实现几乎是相同的(除了执行“FactsBuilder”界面的Builder ):

 public class NutritionFacts { private final int calories; public static class Builder implements FactsBuilder<Builder> { private int calories = 0; public Builder() { } @Override public Builder calories(int val) { return this; } public NutritionFacts build() { return new NutritionFacts(this); } } protected NutritionFacts(Builder builder) { calories = builder.calories; } } 

子类的Builder应该扩展相同的接口(不同的generics实现除外):

 public static class Builder implements FactsBuilder<Builder> { NutritionFacts.Builder baseBuilder; private boolean hasGMO = false; public Builder() { baseBuilder = new NutritionFacts.Builder(); } public Builder GMO(boolean val) { hasGMO = val; return this; } public GMOFacts build() { return new GMOFacts(this); } @Override public Builder calories(int val) { baseBuilder.calories(val); return this; } } 

请注意, NutritionFacts.BuilderGMOFacts.Builder内的一个字段(称为baseBuilder )。 从FactsBuilder接口实现的方法调用baseBuilder的同名方法:

 @Override public Builder calories(int val) { baseBuilder.calories(val); return this; } 

GMOFacts(Builder builder)的构造函数也有很大的改变。 在父类构造函数的构造函数中的第一个调用应该通过适当的NutritionFacts.Builder

 protected GMOFacts(Builder builder) { super(builder.baseBuilder); hasGMO = builder.hasGMO; } 

GMOFacts课程的全面实施:

 public class GMOFacts extends NutritionFacts { private final boolean hasGMO; public static class Builder implements FactsBuilder<Builder> { NutritionFacts.Builder baseBuilder; private boolean hasGMO = false; public Builder() { } public Builder GMO(boolean val) { hasGMO = val; return this; } public GMOFacts build() { return new GMOFacts(this); } @Override public Builder calories(int val) { baseBuilder.calories(val); return this; } } protected GMOFacts(Builder builder) { super(builder.baseBuilder); hasGMO = builder.hasGMO; } } 

你可以做的一件事是在你的每个类中创build一个静态工厂方法:

 NutritionFacts.newBuilder() GMOFacts.newBuilder() 

这个静态工厂方法会返回相应的构build器。 你可以有一个GMOFacts.Builder扩展一个NutritionFacts.Builder ,这不是一个问题。 这里的问题将是处理能见度…