问题
I'm familiar with using the builder pattern with generics and subclassing, but I can't see how to make it work with a non-trivial tree of subclasses (i.e. C extends B extends A). Here's a simple example of what I'm trying to do:
class A {
private final int value;
protected A(ABuilder builder) {
this.value = builder.value;
}
public int getValue() { return value; }
public static class ABuilder<T extends ABuilder<T>> {
private int value;
public T withValue(int value) {
this.value = value;
return (T) this;
}
public A build() {
return new A(this);
}
}
}
class B extends A {
private final String name;
public static BBuilder builder() {
return new BBuilder();
}
protected B(BBuilder builder) {
super(builder);
this.name = builder.name;
}
public String getName() { return name; }
public static class BBuilder<U extends BBuilder<U>> extends ABuilder<BBuilder<U>> {
private String name;
public U withName(String name) {
this.name = name;
return (U) this;
}
public B build() {
return new B(this);
}
}
}
Everything is fine if I declare BBuilder without the generic type:
public static class BBuilder extends ABuilder<BBuilder>
Since I want BBuilder to be extended by a CBuilder, I'm trying to use the same sort of Curiously Recurring Template Pattern as ABuilder. But like this, the compiler sees BBuilder.withValue() as returning an ABuilder, not a BBuilder as I want. This:
B b = builder.withValue(1)
.withName("X")
.build();
doesn't compile. Can anyone see what I'm doing wrong here, I've been going round trying different patterns of generics but can't get it to work.
Thanks to anyone who has any advice.
回答1:
It seems that your mistake only with declaring correct parameter:
class A {
private final int value;
public static <T extends Builder<T>> T builderA() {
return (T)new Builder<>();
}
protected A(Builder<? extends Builder<?>> builder) {
value = builder.value;
}
public static class Builder<T extends Builder<T>> {
private int value;
public T withValue(int value) {
this.value = value;
return (T)this;
}
public A build() {
return new A(this);
}
}
}
class B extends A {
private final String name;
public static <T extends Builder<T>> T builderB() {
return (T)new Builder<>();
}
protected B(Builder<? extends Builder<?>> builder) {
super(builder);
name = builder.name;
}
public static class Builder<T extends Builder<T>> extends A.Builder<T> {
private String name;
public Builder<T> withName(String name) {
this.name = name;
return this;
}
public B build() {
return new B(this);
}
}
}
Client code:
A a = A.builder().withValue(1).build();
B b = B.builder().withValue(2).withName("xx").build();
回答2:
Are you certain you need generics? This hierarchy seems to work fine without generics.
static class A {
protected final int value;
protected A(ABuilder builder) {
this.value = builder.value;
}
public int getValue() {
return value;
}
@Override
public String toString() {
return "A{" +
"value=" + value +
'}';
}
public static ABuilder builder() {
return new ABuilder();
}
public static class ABuilder {
protected int value;
public ABuilder withValue(int value) {
this.value = value;
return this;
}
public A build() {
return new A(this);
}
}
}
static class B extends A {
protected final String name;
protected B(BBuilder builder) {
super(builder);
this.name = builder.name;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "B{" +
"value=" + value +
", name='" + name + '\'' +
'}';
}
public static BBuilder builder() {
return new BBuilder();
}
public static class BBuilder extends ABuilder {
private String name;
public BBuilder withName(String name) {
this.name = name;
return this;
}
@Override
public BBuilder withValue(int value) {
this.value = value * 2;
return this;
}
public B build() {
return new B(this);
}
}
}
static class C extends B {
private final String otherName;
protected C(CBuilder builder) {
super(builder);
this.otherName = builder.otherName;
}
public String getName() {
return otherName;
}
@Override
public String toString() {
return "C{" +
"value=" + value +
", name='" + name + '\'' +
", otherName='" + otherName + '\'' +
'}';
}
public static CBuilder builder() {
return new CBuilder();
}
public static class CBuilder extends BBuilder {
private String otherName;
public CBuilder withName(String name) {
this.otherName = name;
return this;
}
public C build() {
return new C(this);
}
}
}
public void test() {
A a = A.builder().withValue(10).build();
B b = B.builder().withValue(10).withName("B").build();
C c = C.builder().withName("C").build();
System.out.println("a = "+a);
System.out.println("b = "+b);
System.out.println("c = "+c);
}
来源:https://stackoverflow.com/questions/50838999/java-builder-pattern-with-non-trivial-subclass-tree