对 JavaBuilder 类进行子类化

给定 这篇多布斯医生的文章,特别是生成器模式,我们如何处理生成器子类化的情况?对于我们想要子类化以添加转基因标签的例子,一个简单的实现是:

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,那么我们如何解决这个问题,或者有更好的模式可以使用?

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

88844 次浏览

您可以在每个类中创建一个静态工厂方法:

NutritionFacts.newBuilder()
GMOFacts.newBuilder()

然后,这个静态工厂方法将返回适当的构建器。你可以让一个 GMOFacts.Builder扩展一个 NutritionFacts.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 Builder calories(int val)
{ super.calories(val); return this; }
public GMOFacts build() {
return new GMOFacts(this);
}
}
[...]
}

你可以用泛型来解决它。我想这就是所谓的 “奇怪的循环模式”

将基类生成器方法的返回类型设置为泛型参数。

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

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

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

顺便说一句,为了摆脱

unchecked or unsafe operations警告

对于 return (T) this;语句@dimadima 和@Thomas N. 来说,下面的解决方案适用于某些情况。

使 abstract成为声明泛型类型(在本例中为 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 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();
}
}

以及父类:

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 + '\'' +
'}';
}
}

Key points:

  • 在生成器中封装对象,以便继承阻止您设置父类型中保存的对象的字段
  • 调用以超级确保添加到超级类型生成器方法中的逻辑(如果有的话)保留在子类型中。
  • 不利的一面是在父类中虚假的对象创建... ... 但是请参见下面的清理方法
  • 正面更容易一目了然,而且没有冗长的构造函数传输属性。
  • 如果您有多个线程访问您的构建器对象... ... 我想我很高兴我不是您:)。

编辑:

我找到了一个绕过伪造对象创建的方法。首先将这个添加到每个构建器:

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

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

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

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

还有另一种根据 Builder模式创建类的方法,它符合“优先组合而不是继承”的原则。

定义父类 Builder将继承的接口:

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应该扩展相同的接口(不同的泛型实现除外) :

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 ()返回的对象类型,其次是每个可选参数 setter 返回的类型。下列为家长及子女的课程,以供参考:

// **Parent**
public abstract static class Builder<T, U extends Builder<T, U>> {
// Required parameters
private final String name;


// Optional parameters
private List<String> outputFields = null;




public Builder(String pName) {
name = pName;
}


public U outputFields(List<String> pOutFlds) {
outputFields = new ArrayList<>(pOutFlds);
return getThis();
}




/**
* This helps avoid "unchecked warning", which would forces to cast to "T" in each of the optional
* parameter setters..
* @return
*/
abstract U getThis();


public abstract T build();






/*
* Getters
*/
public String getName() {
return name;
}
}


// **Child**
public static class Builder extends AbstractRule.Builder<ContextAugmentingRule, ContextAugmentingRule.Builder> {
// Required parameters
private final Map<String, Object> nameValuePairsToAdd;


// Optional parameters
private String fooBar;




Builder(String pName, Map<String, String> pNameValPairs) {
super(pName);
/**
* Must do this, in case client code (I.e. JavaScript) is re-using
* the passed in for multiple purposes. Doing {@link Collections#unmodifiableMap(Map)}
* won't caught it, because the backing Map passed by client prior to wrapping in
* unmodifiable Map can still be modified.
*/
nameValuePairsToAdd = new HashMap<>(pNameValPairs);
}


public Builder fooBar(String pStr) {
fooBar = pStr;
return this;
}




@Override
public ContextAugmentingRule build() {
try {
Rule r = new ContextAugmentingRule(this);
storeInRuleByNameCache(r);
return (ContextAugmentingRule) r;
} catch (RuleException e) {
throw new IllegalArgumentException(e);
}
}


@Override
Builder getThis() {
return this;
}
}

这个已经满足了我的需求。

一个完整的3级多构建器继承示例如下 :

(有关带有构建器复制建构子的版本,请参阅下面的第二个示例)

第一级-父级(可能是抽象的)

import lombok.ToString;


@ToString
@SuppressWarnings("unchecked")
public abstract class Class1 {
protected int f1;


public static class Builder<C extends Class1, B extends Builder<C, B>> {
C obj;


protected Builder(C constructedObj) {
this.obj = constructedObj;
}


B f1(int f1) {
obj.f1 = f1;
return (B)this;
}


C build() {
return obj;
}
}
}

第二层

import lombok.ToString;


@ToString(callSuper=true)
@SuppressWarnings("unchecked")
public class Class2 extends Class1 {
protected int f2;


public static class Builder<C extends Class2, B extends Builder<C, B>> extends Class1.Builder<C, B> {
public Builder() {
this((C) new Class2());
}


protected Builder(C obj) {
super(obj);
}


B f2(int f2) {
obj.f2 = f2;
return (B)this;
}
}
}

第三层

import lombok.ToString;


@ToString(callSuper=true)
@SuppressWarnings("unchecked")
public class Class3 extends Class2 {
protected int f3;


public static class Builder<C extends Class3, B extends Builder<C, B>> extends Class2.Builder<C, B> {
public Builder() {
this((C) new Class3());
}


protected Builder(C obj) {
super(obj);
}


B f3(int f3) {
obj.f3 = f3;
return (B)this;
}
}
}

还有一个用法的例子

public class Test {
public static void main(String[] args) {
Class2 b1 = new Class2.Builder<>().f1(1).f2(2).build();
System.out.println(b1);
Class2 b2 = new Class2.Builder<>().f2(2).f1(1).build();
System.out.println(b2);


Class3 c1 = new Class3.Builder<>().f1(1).f2(2).f3(3).build();
System.out.println(c1);
Class3 c2 = new Class3.Builder<>().f3(3).f1(1).f2(2).build();
System.out.println(c2);
Class3 c3 = new Class3.Builder<>().f3(3).f2(2).f1(1).build();
System.out.println(c3);
Class3 c4 = new Class3.Builder<>().f2(2).f3(3).f1(1).build();
System.out.println(c4);
}
}


一个更长的版本,为建造者设计了一个复制建构子:

第一级-父级(可能是抽象的)

import lombok.ToString;


@ToString
@SuppressWarnings("unchecked")
public abstract class Class1 {
protected int f1;


public static class Builder<C extends Class1, B extends Builder<C, B>> {
C obj;


protected void setObj(C obj) {
this.obj = obj;
}


protected void copy(C obj) {
this.f1(obj.f1);
}


B f1(int f1) {
obj.f1 = f1;
return (B)this;
}


C build() {
return obj;
}
}
}

第二层

import lombok.ToString;


@ToString(callSuper=true)
@SuppressWarnings("unchecked")
public class Class2 extends Class1 {
protected int f2;


public static class Builder<C extends Class2, B extends Builder<C, B>> extends Class1.Builder<C, B> {
public Builder() {
setObj((C) new Class2());
}


public Builder(C obj) {
this();
copy(obj);
}


@Override
protected void copy(C obj) {
super.copy(obj);
this.f2(obj.f2);
}


B f2(int f2) {
obj.f2 = f2;
return (B)this;
}
}
}

第三层

import lombok.ToString;


@ToString(callSuper=true)
@SuppressWarnings("unchecked")
public class Class3 extends Class2 {
protected int f3;


public static class Builder<C extends Class3, B extends Builder<C, B>> extends Class2.Builder<C, B> {
public Builder() {
setObj((C) new Class3());
}


public Builder(C obj) {
this();
copy(obj);
}


@Override
protected void copy(C obj) {
super.copy(obj);
this.f3(obj.f3);
}


B f3(int f3) {
obj.f3 = f3;
return (B)this;
}
}
}

还有一个用法的例子

public class Test {
public static void main(String[] args) {
Class3 c4 = new Class3.Builder<>().f2(2).f3(3).f1(1).build();
System.out.println(c4);


// Class3 builder copy
Class3 c42 = new Class3.Builder<>(c4).f2(12).build();
System.out.println(c42);
Class3 c43 = new Class3.Builder<>(c42).f2(22).f1(11).build();
System.out.println(c43);
Class3 c44 = new Class3.Builder<>(c43).f3(13).f1(21).build();
System.out.println(c44);
}
}

下面的 IEEE 贡献 Java 语言中的流畅生成器为这个问题提供了一个全面的解决方案。

它将原始问题分解为 遗传缺陷拟不变性拟不变性的两个子问题,并展示了这两个子问题的解决方案如何通过 Java 中的经典构建器模式中的代码重用打开继承支持。