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

Ril*_*ark 22 java design-patterns builder

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

因此,假设以下定义:

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();
}
Run Code Online (Sandbox Code Playgroud)

我怎样才能有效地实施构建器Child1BuilderChild2Builder?他们应该支持以下操作:

Child1 child1 = Child1Builder.newChild1().withChild1Property(5).withParentProperty(10L);
Run Code Online (Sandbox Code Playgroud)

Child2 child2 = Child2Builder.newChild2().withChild2PropertyA("Hello").withParentProperty(10L).withChild2PropertyB(new Object());
Run Code Online (Sandbox Code Playgroud)

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

编辑添加第二个属性,Child2以澄清这不能用简单的泛型.我不是在寻找一种方式来组合Child1Child2-我正在寻找一种方式来实现Builder,不重复建设的父类为每个子类的工作体系.

谢谢你的帮助!

seh*_*seh 22

我想象的解决方案就像奇怪的重复模板模式,或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_;
}
Run Code Online (Sandbox Code Playgroud)

如您所见,ParentBuilder类型期望与派生类型协作以允许它返回正确类型的实例.自己的this参考不会到期,因为类型this之内ParentBuilder,当然,ParentBuilder而不是,比方说,Child1Builder如预期维持"流利"的呼叫链接.

我欠Angelika Langer的教程条目 " getThis()技巧" .

  • 啊哈,`getThis()`技巧是我失踪的一步!我不介意额外的样板 - 我只是想避免为我稍后添加到父级的每个字段复制(和维护!)代码.谢谢你的回答. (3认同)

Dav*_*les 8

如果你愿意接受从"最年轻"到"最老" 的方法被称为限制,我认为getParent()并且getThis()是必要的withXXXProperty():

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

  • 这些都是很好的观点,大卫。当我要求能够以任何顺序调用`setPropertyX` 方法时,我可能有点迂腐。接受的答案绝对难以阅读。 (2认同)