Java中是否支持用建造者模式创建多态对象层次结构?

27

我有一个接口层次结构,其中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中。 我不是在寻找将Child1Child2合并的方法 - 我正在寻找一种实现Builder系统的方法,该方法不会为每个子类重复构建父类的工作。

感谢任何帮助!


1
如果Child1和Child2的实现都继承自同一个也实现了Parent的类,那么你可以有一个父Builder,它具有方法withParentProperty() - Ivo
但是父级构建器将返回“Parent”引用,而不是适当的子引用,对吗? - Riley Lark
1
一旦你尝试实现,你会发现它比这更复杂,你需要像下面提供的seh那样的东西。如果你能展示一个更简单的实现,我很乐意看到它! - Riley Lark
6个回答

24
我想象中的解决方案类似于奇妙递归模板模式,或者称为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 extends ParentBuilder<T>>
{
  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<Child1Builder>
{
  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 引用不起作用,因为在 ParentBuilder 中,this 的类型当然是 ParentBuilder,而不是像维护“流畅”调用链一样的 Child1Builder

我要感谢 Angelika Langer's tutorial entry 中的“getThis() 技巧”。


3
啊哈,getThis() 技巧是我之前缺失的一步!我不介意额外的样板代码 - 我只想避免每次向父类添加字段时都需要复制(和维护!)代码。感谢您的回答。 - Riley Lark
1
有一篇关于类似解决方案的不错的Java.net博客文章:http://weblogs.java.net/blog/emcmanus/archive/2010/10/25/using-builder-pattern-subclasses。 - Håvard Geithus
如果您想添加另一个嵌套的Child1具体子类,例如Child2和Child2Builder,您将如何声明Child2Builder和Child1Builders getThis方法? - claya
脑海中浮现了两件事:ConcreteChild1类和Child1Builder类都不能是final的,而且Child1Builder需要一个类型参数来指示它构建的具体类型,这可以是ConcreteChild1或您提出的ConcreteChild2。由于Java缺乏类型参数的默认值,直接使用Child1Builder类可能会更麻烦。引入一个介于抽象带参数化类可能有所帮助,这个类既可以扩展Child1Builder也可以扩展Child2Builder - seh
@seh,感谢您的见解。 这个中间的抽象类会提供什么?getThis()方法吗?似乎需要从可实例化的构建器类中获取,而不是从抽象类中获取。它只是为类的类型参数服务的吗?例如:class Child1Builder extends ParentBuilder<AbstractChildBuilder> - claya
claya,经过几个月的尝试,我唯一找到的解决方案是将中间类设为抽象类。因此,类树只在叶子节点处有具体类,其他地方没有。 - LegendLength

9

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

class Parent
{
    private final long parentProperty;

    public long getParentProperty()
    {
        return parentProperty;
    }

    public static abstract class Builder<T extends Parent>
    {
        private long parentProperty;

        public Builder<T> withParentProperty( long parentProperty )
        {
            this.parentProperty = parentProperty;
            return this;
        }

        public abstract T build();
    }

    public static Builder<?> builder()
    {
        return new Builder<Parent>()
        {
            @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<T extends Child1> extends Parent.Builder<T>
    {
        private int child1Property;

        public Builder<T> withChild1Property( int child1Property )
        {
            this.child1Property = child1Property;
            return this;
        }

        public abstract T build();
    }

    public static Builder<?> builder()
    {
        return new Builder<Child1>()
        {
            @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<T extends Child2> extends Parent.Builder<T>
    {
        private String child2PropertyA;
        private Object child2PropertyB;

        public Builder<T> withChild2PropertyA( String child2PropertyA )
        {
            this.child2PropertyA = child2PropertyA;
            return this;
        }

        public Builder<T> withChild2PropertyB( Object child2PropertyB )
        {
            this.child2PropertyB = child2PropertyB;
            return this;
        }
    }

    public static Builder<?> builder()
    {
        return new Builder<Child2>()
        {
            @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()”调用。(注意:这假定每个级别都设计为进一步可继承。如果在任何时候你有一个“final”后代,则可以使构建器类具体化并将构造函数设置为私有。)但我认为这个版本更容易Follow,对于下一个程序员来说,他必须维护您的代码(首先没有自引用泛型;一个“Builder<X>”构建“Xs”)。在我的看法中,在设置父属性之前要求设置子属性与其在灵活性上的劣势相比,在一致性方面也有优点。

2
这些是很好的观点,David。也许我有点学究,但我要求能够以任何顺序调用setPropertyX方法。被接受的答案确实很难阅读。 - Riley Lark

2
这里有一个使用泛型的解决方案。原始答案翻译成"最初的回答"。
public abstract class ParentBuilder<T extends ParentBuilder<T>> {
    private long parentProperty;

    protected abstract T self();

    public T withParentProperty(long parentProperty) {
        this.parentProperty = parentProperty;
        return self();
    }

    protected static class SimpleParent implements Parent {
        private long parentProperty;

        public SimpleParent(ParentBuilder<?> builder) {
            this.parentProperty = builder.parentProperty;
        }

        @Override
        public Long getParentProperty() {
            return parentProperty;
        }
    }
}

public final class Child1Builder extends ParentBuilder<Child1Builder> {
    private int child1Property;

    private Child1Builder() {}

    public static Child1Builder newChild1() {
        return new Child1Builder();
    }

    @Override
    protected Child1Builder self() {
        return this;
    }

    public Child1Builder withChild1Property(int child1Property) {
        this.child1Property = child1Property;
        return self();
    }

    public Child1 build() {
        return new SimpleChild1(this);
    }

    private final class SimpleChild1 extends SimpleParent implements Child1 {
        private int child1Property;

        public SimpleChild1(Child1Builder builder) {
            super(builder);
            this.child1Property = builder.child1Property;
        }

        @Override
        public Integer getChild1Property() {
            return child1Property;
        }
    }
}

public final class Child2Builder extends ParentBuilder<Child2Builder> {

    private String child2propertyA;
    private Object child2propertyB;

    private Child2Builder() {}

    public static Child2Builder newChild2() {
        return new Child2Builder();
    }

    @Override
    protected Child2Builder self() {
        return this;
    }

    public Child2Builder withChild2PropertyA(String child2propertyA) {
        this.child2propertyA = child2propertyA;
        return self();
    }

    public Child2Builder withChild2PropertyB(Object child2propertyB) {
        this.child2propertyB = child2propertyB;
        return self();
    }

    public Child2 build() {
        return new SimpleChild2(this);
    }

    private static final class SimpleChild2 extends SimpleParent implements Child2 {
        private String child2propertyA;
        private Object child2propertyB;

        public SimpleChild2(Child2Builder builder) {
            super(builder);
            this.child2propertyA = builder.child2propertyA;
            this.child2propertyB = builder.child2propertyB;
        }

        @Override
        public String getChild2PropertyA() {
            return child2propertyA;
        }

        @Override
        public Object getChild2PropertyB() {
            return child2propertyB;
        }
    }
}

对于更大的层次结构或其中具体类不仅在叶子节点上的情况,有必要将上述具体构造器的一部分提取到一个中间抽象类中。例如,Child1Builder 可以被拆分成以下两个类 Child1BuilderAbstractChild1Builder,后者可以由另一个子构造器进行扩展。


public abstract class AbstractChild1Builder<T extends AbstractChild1Builder<T>> extends ParentBuilder<T> {

    protected int child1Property;

    public T withChild1Property(int child1Property) {
        this.child1Property = child1Property;
        return self();
    }

    protected final class SimpleChild1 extends SimpleParent implements Child1 {
        private int child1Property;

        public SimpleChild1(AbstractChild1Builder<Child1Builder> builder) {
            super(builder);
            this.child1Property = builder.child1Property;
        }

        @Override
        public Integer getChild1Property() {
            return child1Property;
        }
    }
}

public final class Child1Builder extends AbstractChild1Builder<Child1Builder> {
    private Child1Builder() {}

    public static AbstractChild1Builder<Child1Builder> newChild1() {
        return new Child1Builder();
    }

    @Override
    protected Child1Builder self() {
        return this;
    }

    public Child1 build() {
        return new SimpleChild1(this);
    }   
}

1
谢谢您展示如何将模式扩展到多个对象的多代中,通过将Builder类分成两个部分。正是我正在寻找的。 - Dave Mulligan

1

也许可以不用构建器来实现这个功能?

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

这是一个简单得多的解决方案 - 感谢您的编写!它会生成可变对象,而我正试图避免这种情况,并且仍然需要我在每个子对象中实现setParentProperty(对于每个父属性!),因此我认为我可能需要更复杂的东西。 - Riley Lark
将对象变为不可变的需要为每种子类型编写两个类。这样可以吗?您能接受显式构造函数并且不进行设置属性链接吗?这些属性可以放在一个映射中吗?此外,每个子类中setParentProperty的实现只需要几行代码。在这种情况下的替代方案将是一个丑陋的向下转型。 - Ray Tayek
Seh提供的替代方案只需要对每个子类进行一些访问级别调整(在同一个包中作为构建器),是吗?在每个子类中实现setParentProperty只需要几行代码,但当我添加/删除父属性时,维护成本会越来越高。在我的实际情况下,有五个父属性和八个子类,这就是40个额外的方法,因此seh建议的额外getThis和getParent相比非常轻量级。如果我需要添加一个父属性,则不需要更改子类。 - Riley Lark
好的。考虑在父类和每个子类中使用两个映射:propertyNameToType 和 propertyNameToValue。轻松构建它们,并将不可变映射粘贴到向用户公开的类中。 - Ray Tayek
不,setter函数可以检查新值是否为正确的类型,因为我们有propertyNameToType映射。但是,使用getter函数则需要进行类型转换。 - Ray Tayek
我发布了另一个使用地图的答案,但你必须像这样获取值:c2.getChildProperty(C2.Names.x); 和 c2.getParentProperty(P.Names.d); - Ray Tayek

0

使用泛型,如下所示:

public interface Parent {
    public Long getParentProperty();
}

public interface Child<T> {
    public T getChildProperty(); 
}

然后,不要使用Child1,而是使用Child<Integer>,不要使用Child2,而是使用Child<String>


2
这是在这种简单情况下实现Child1和Child2的好方法,但它并没有解决我关于如何高效地实现构建器而不重复代码的核心问题。以这种方式使用泛型不支持我原始帖子中的操作。 - Riley Lark
1
我相信他关于泛型的观点是,你可以将大部分Child逻辑整合到一个类中,这样一来定义父级构建逻辑就变得非常简单。 - Perception
1
不,这只是一个例子。在实际情况中,我不能将大量的子逻辑合并到一个类中。我想要有不同的类来做不同的事情,但它们都有一个共同的父类。 - Riley Lark

0
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<Names, Object> 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<Names, Object> parentNameToValue = new EnumMap<Names, Object>(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<C1.Names, Object> 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<C1.Names, Object> nameToValue = new EnumMap<C1.Names, Object>(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<C2.Names, Object> 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<C2.Names, Object> nameToValue = new EnumMap<C2.Names, Object>(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);
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接