多态对象层次结构的构建器模式:Java可能吗?

我有一个接口层次结构, Child实现Parent 。 我想使用不可变对象,所以我想设计构建这些对象的Builder类。 但是,我有许多Child接口,我不想重复在每种类型的子构建器中构建Parent的代码。

因此,假设以下定义:

 public interface Parent { public Long getParentProperty(); } public interface Child1 extends Parent { public Integer getChild1Property(); } public interface Child2 extends Parent { public String getChild2PropertyA(); public Object getChild2PropertyB(); } 

如何有效地实现构建器Child1BuilderChild2Builder ? 他们应该支持以下操作:

 Child1 child1 = Child1Builder.newChild1().withChild1Property(5).withParentProperty(10L); 

 Child2 child2 = Child2Builder.newChild2().withChild2PropertyA("Hello").withParentProperty(10L).withChild2PropertyB(new Object()); 

我不想为每个子构建器实现withParentProperty的特殊情况。

编辑为Child2添加第二个属性,以澄清这不能用简单的generics。 我不是在寻找一种方法来组合Child2Child2 – 我正在寻找一种方法来实现一个不会复制为每个子类构建父类的工作的Builder系统。

谢谢你的帮助!

我想象的解决方案就像奇怪的重复模板模式 ,或CRTP 。 您可以定义一个基类来处理与父相关的初始化,但是您仍然可能发现两个样板getParent()getThis()方法在每个派生的与子相关的构建器类中重复过多。

看一看:

 abstract class ParentBase implements Parent { @Override public final Long getParentProperty() { return parentProperty_; } protected void setParentProperty(Long value) { parentProperty_ = value; } private Long parentProperty_; } abstract class ParentBuilder> { T withParentProperty(Long value) { getParent().setParentProperty(value); return getThis(); } protected abstract ParentBase getParent(); protected abstract T getThis(); } final class ConcreteChild1 extends ParentBase implements Child1 { @Override public Integer getChild1Property() { return childProperty_; } public void setChild1Property(Integer value) { childProperty_ = value; } private Integer childProperty_; } final class Child1Builder extends ParentBuilder { public Child1Builder() { pending_ = new ConcreteChild1(); } public Child1Builder withChild1Property(Integer value) { pending_.setChild1Property(value); return this; } @Override protected ParentBase getParent() { return pending_; } @Override protected Child1Builder getThis() { return this; } private final ConcreteChild1 pending_; } 

如您所见, ParentBuilder类型期望与派生类型协作以允许它返回正确类型的实例。 它自己的this引用将不会到期,因为ParentBuilderthis类型当然是ParentBuilder ,而不是,例如, Child1Builder是为了保持“流畅”的调用链。

我欠Angelika Langer教程条目的“ getThis()技巧”。

我不认为getParent()getThis()是必要的,如果你愿意接受从“最年轻”到“最老”调用withXXXProperty()方法的限制:

 class Parent { private final long parentProperty; public long getParentProperty() { return parentProperty; } public static abstract class Builder { private long parentProperty; public Builder withParentProperty( long parentProperty ) { this.parentProperty = parentProperty; return this; } public abstract T build(); } public static Builder builder() { return new Builder() { @Override public Parent build() { return new Parent(this); } }; } protected Parent( Builder builder ) { this.parentProperty = builder.parentProperty; } } class Child1 extends Parent { private final int child1Property; public int getChild1Property() { return child1Property; } public static abstract class Builder extends Parent.Builder { private int child1Property; public Builder withChild1Property( int child1Property ) { this.child1Property = child1Property; return this; } public abstract T build(); } public static Builder builder() { return new Builder() { @Override public Child1 build() { return new Child1(this); } }; } protected Child1( Builder builder ) { super(builder); this.child1Property = builder.child1Property; } } class Child2 extends Parent { private final String child2PropertyA; private final Object child2PropertyB; public String getChild2PropertyA() { return child2PropertyA; } public Object getChild2PropertyB() { return child2PropertyB; } public static abstract class Builder extends Parent.Builder { private String child2PropertyA; private Object child2PropertyB; public Builder withChild2PropertyA( String child2PropertyA ) { this.child2PropertyA = child2PropertyA; return this; } public Builder withChild2PropertyB( Object child2PropertyB ) { this.child2PropertyB = child2PropertyB; return this; } } public static Builder builder() { return new Builder() { @Override public Child2 build() { return new Child2(this); } }; } protected Child2( Builder builder ) { super(builder); this.child2PropertyA = builder.child2PropertyA; this.child2PropertyB = builder.child2PropertyB; } } class BuilderTest { public static void main( String[] args ) { Child1 child1 = Child1.builder() .withChild1Property(-3) .withParentProperty(5L) .build(); Child2 grandchild = Child2.builder() .withChild2PropertyA("hello") .withChild2PropertyB(new Object()) .withParentProperty(10L) .build(); } } 

这里还有一些样板:每个builder()方法中的匿名具体Builder ,以及每个构造函数中的super()调用。 (注意:假设每个级别都是为了进一步inheritance而设计的。如果在任何时候你有final后代,你可以使构建器类具体化,构造函数是私有的。)

但我认为这个版本更容易理解,对于下一个程序员来说,他必须维护你的代码(没有自引用generics,对于初学者; Builder构建Xs )。 并且IMHO要求在父属性之前在构建器上设置子属性在一致性方面同样是一个优点,因为它在灵活性方面是不利的。

也许这样没有建设者?:

 interface P { public Long getParentProperty(); } interface C1 extends P { public Integer getChild1Property(); } interface C2 extends P { public String getChild2PropertyA(); public Object getChild2PropertyB(); } abstract class PABC implements P { @Override public final Long getParentProperty() { return parentLong; } protected Long parentLong; protected PABC setParentProperty(Long value) { parentLong = value; return this; } } final class C1Impl extends PABC implements C1 { protected C1Impl setParentProperty(Long value) { super.setParentProperty(value); return this; } @Override public Integer getChild1Property() { return n; } public C1Impl setChild1Property(Integer value) { n = value; return this; } private Integer n; } final class C2Impl extends PABC implements C2 { private String string; private Object object; protected C2Impl setParentProperty(Long value) { super.setParentProperty(value); return this; } @Override public String getChild2PropertyA() { return string; } @Override public Object getChild2PropertyB() { return object; } C2Impl setChild2PropertyA(String string) { this.string=string; return this; } C2Impl setChild2PropertyB(Object o) { this.object=o; return this; } } public class Myso9138027 { public static void main(String[] args) { C1Impl c1 = new C1Impl().setChild1Property(5).setParentProperty(10L); C2Impl c2 = new C2Impl().setChild2PropertyA("Hello").setParentProperty(10L).setChild2PropertyB(new Object()); } } 

使用generics,如下:

 public interface Parent { public Long getParentProperty(); } public interface Child { public T getChildProperty(); } 

然后使用Child代替Child1,而不是Child2,使用Child

 package so9138027take2; import java.util.*; import so9138027take2.C2.Names; interface P { public Object getParentProperty(Names name); enum Names { i(Integer.class), d(Double.class), s(String.class); Names(Class clazz) { this.clazz = clazz; } final Class clazz; } } interface C1 extends P { public Object getChildProperty(Names name); enum Names { a(Integer.class), b(Double.class), c(String.class); Names(Class clazz) { this.clazz = clazz; } final Class clazz; } } interface C2 extends P { public Object getChildProperty(Names name); enum Names { x(Integer.class), y(Double.class), z(String.class); Names(Class clazz) { this.clazz = clazz; } final Class clazz; } } abstract class PABCImmutable implements P { public PABCImmutable(PABC parent) { parentNameToValue = Collections.unmodifiableMap(parent.parentNameToValue); } @Override public final Object getParentProperty(Names name) { return parentNameToValue.get(name); } public String toString() { return parentNameToValue.toString(); } final Map parentNameToValue; } abstract class PABC implements P { @Override public final Object getParentProperty(Names name) { return parentNameToValue.get(name); } protected PABC setParentProperty(Names name, Object value) { if (name.clazz.isInstance(value)) parentNameToValue.put(name, value); else throw new RuntimeException("value is not valid for " + name); return this; } public String toString() { return parentNameToValue.toString(); } EnumMap parentNameToValue = new EnumMap(P.Names.class); } final class C1Immutable extends PABCImmutable implements C1 { public C1Immutable(C1Impl c1) { super(c1); nameToValue = Collections.unmodifiableMap(c1.nameToValue); } @Override public Object getChildProperty(C1.Names name) { return nameToValue.get(name); } public String toString() { return super.toString() + nameToValue.toString(); } final Map nameToValue; } final class C1Impl extends PABC implements C1 { @Override public Object getChildProperty(C1.Names name) { return nameToValue.get(name); } public Object setChildProperty(C1.Names name, Object value) { if (name.clazz.isInstance(value)) nameToValue.put(name, value); else throw new RuntimeException("value is not valid for " + name); return this; } public String toString() { return super.toString() + nameToValue.toString(); } EnumMap nameToValue = new EnumMap(C1.Names.class); } final class C2Immutable extends PABCImmutable implements C2 { public C2Immutable(C2Impl c2) { super(c2); this.nameToValue = Collections.unmodifiableMap(c2.nameToValue); } @Override public Object getChildProperty(C2.Names name) { return nameToValue.get(name); } public String toString() { return super.toString() + nameToValue.toString(); } final Map nameToValue; } final class C2Impl extends PABC implements C2 { @Override public Object getChildProperty(C2.Names name) { return nameToValue.get(name); } public Object setChildProperty(C2.Names name, Object value) { if (name.clazz.isInstance(value)) { nameToValue.put(name, value); } else { System.out.println("name=" + name + ", value=" + value); throw new RuntimeException("value is not valid for " + name); } return this; } public String toString() { return super.toString() + nameToValue.toString(); } EnumMap nameToValue = new EnumMap(C2.Names.class); } public class So9138027take2 { public static void main(String[] args) { Object[] parentValues = new Object[] { 1, 2., "foo" }; C1Impl c1 = new C1Impl(); Object[] c1Values = new Object[] { 3, 4., "bar" }; for (P.Names name : P.Names.values()) c1.setParentProperty(name, parentValues[name.ordinal()]); for (C1.Names name : C1.Names.values()) c1.setChildProperty(name, c1Values[name.ordinal()]); C2Impl c2 = new C2Impl(); Object[] c2Values = new Object[] { 5, 6., "baz" }; for (P.Names name : P.Names.values()) c2.setParentProperty(name, parentValues[name.ordinal()]); for (C2.Names name : C2.Names.values()) c2.setChildProperty(name, c2Values[name.ordinal()]); C1 immutableC1 = new C1Immutable(c1); System.out.println("child 1: "+immutableC1); C2 immutableC2 = new C2Immutable(c2); System.out.println("child 2: "+immutableC2); } }