构建器模式和继承

时间:2014-01-13 08:03:30

标签: java design-patterns inheritance builder

我有一个对象层次结构,随着继承树的加深,复杂性会增加。这些都不是抽象的,因此,它们的所有实例都具有或多或少的复杂目的。

由于参数的数量非常多,我想使用Builder Pattern来设置属性而不是编码几个构造函数。由于我需要满足所有排列,我的继承树中的叶子类将有伸缩构造函数。

当我在设计中遇到一些问题时,我已经在这里浏览了一个答案。首先,让我举一个简单,浅薄的例子来说明问题。

public class Rabbit
{
    public String sex;
    public String name;

    public Rabbit(Builder builder)
    {
        sex = builder.sex;
        name = builder.name;
    }

    public static class Builder
    {
        protected String sex;
        protected String name;

        public Builder() { }

        public Builder sex(String sex)
        {
            this.sex = sex;
            return this;
        }

        public Builder name(String name)
        {
            this.name = name;
            return this;
        }

        public Rabbit build()
        {
            return new Rabbit(this);
        }
    }
}

public class Lop extends Rabbit
{
    public float earLength;
    public String furColour;

    public Lop(LopBuilder builder)
    {
        super(builder);
        this.earLength = builder.earLength;
        this.furColour = builder.furColour;
    }

    public static class LopBuilder extends Rabbit.Builder
    {
        protected float earLength;
        protected String furColour;

        public LopBuilder() { }

        public Builder earLength(float length)
        {
            this.earLength = length;
            return this;
        }

        public Builder furColour(String colour)
        {
            this.furColour = colour;
            return this;
        }

        public Lop build()
        {
            return new Lop(this);
        }
    }
}

既然我们已经有了一些代码,那么我需要构建一个Lop

Lop lop = new Lop.LopBuilder().furColour("Gray").name("Rabbit").earLength(4.6f);

此调用无法编译,因为无法解析最后一个链式调用,Builder未定义方法earLength。因此,这种方式要求所有调用都以特定的顺序链接,这是非常不切实际的,特别是对于深层次结构树。

现在,在我搜索答案时,我遇到了Subclassing a Java Builder class,建议使用Curiously Recursive Generic Pattern。但是,由于我的层次结构不包含抽象类,因此该解决方案对我不起作用。但是这种方法依赖于抽象和多态来起作用,这就是为什么我不相信我可以根据我的需要调整它。

我目前采用的方法是覆盖层次结构中超类Builder的所有方法,然后执行以下操作:

public ConcreteBuilder someOverridenMethod(Object someParameter)
{
    super(someParameter);
    return this;
}

通过这种方法,我可以向我保证返回一个我可以发出链接调用的实例。虽然这并不像Telescoping Anti-pattern那么糟糕,但它是一个接近的第二,我认为它有点“hacky”。​​

我的问题有另一种解决方案,我不知道吗?优选地,解决方案与设计模式一致。谢谢!

8 个答案:

答案 0 :(得分:50)

这对于递归绑定肯定是可能的,但是子类型构建器也需要是通用的,并且您需要一些临时抽象类。它有点麻烦,但它仍然比非通用版本更容易。

/**
 * Extend this for Mammal subtype builders.
 */
abstract class GenericMammalBuilder<B extends GenericMammalBuilder<B>> {
    String sex;
    String name;

    B sex(String sex) {
        this.sex = sex;
        return self();
    }

    B name(String name) {
        this.name = name;
        return self();
    }

    abstract Mammal build();

    @SuppressWarnings("unchecked")
    final B self() {
        return (B) this;
    }
}

/**
 * Use this to actually build new Mammal instances.
 */
final class MammalBuilder extends GenericMammalBuilder<MammalBuilder> {
    @Override
    Mammal build() {
        return new Mammal(this);
    }
}

/**
 * Extend this for Rabbit subtype builders, e.g. LopBuilder.
 */
abstract class GenericRabbitBuilder<B extends GenericRabbitBuilder<B>>
        extends GenericMammalBuilder<B> {
    Color furColor;

    B furColor(Color furColor) {
        this.furColor = furColor;
        return self();
    }

    @Override
    abstract Rabbit build();
}

/**
 * Use this to actually build new Rabbit instances.
 */
final class RabbitBuilder extends GenericRabbitBuilder<RabbitBuilder> {
    @Override
    Rabbit build() {
        return new Rabbit(this);
    }
}

有一种方法可以避免使用“具体”叶子类,如果我们有这个:

class MammalBuilder<B extends MammalBuilder<B>> {
    ...
}
class RabbitBuilder<B extends RabbitBuilder<B>>
        extends MammalBuilder<B> {
    ...
}

然后,您需要使用菱形创建新实例,并在引用类型中使用通配符:

static RabbitBuilder<?> builder() {
    return new RabbitBuilder<>();
}

这是有效的,因为类型变量的绑定确保了所有方法,例如, RabbitBuilder的返回类型为RabbitBuilder,即使type参数只是通配符。

但是,我并不喜欢这样,因为你需要在任何地方使用通配符,而且你只能使用菱形或raw type创建一个新实例。我想你最终会有一点尴尬。


顺便说一下,关于这个:

@SuppressWarnings("unchecked")
final B self() {
    return (B) this;
}

有一种方法可以避免这种未经检查的强制转换,即使方法具有抽象性:

abstract B self();

然后在叶子类中重写它:

@Override
RabbitBuilder self() { return this; }

这样做的问题是虽然它更加类型安全,但子类可以返回this以外的其他内容。基本上,无论哪种方式,子类都有机会做错事,所以我真的没有理由更喜欢其中一种方法而不是另一种方法。

答案 1 :(得分:2)

如果有人仍遇到同样的问题,我建议采用以下解决方案,即符合“首选合成而不是继承”的设计模式。

家长班

它的主要元素是父类Builder必须实现的接口:

public interface RabbitBuilder<T> {
    public T sex(String sex);
    public T name(String name);
}

以下是更改后的父类:

public class Rabbit {
    public String sex;
    public String name;

    public Rabbit(Builder builder) {
        sex = builder.sex;
        name = builder.name;
    }

    public static class Builder implements RabbitBuilder<Builder> {
        protected String sex;
        protected String name;

        public Builder() {}

        public Rabbit build() {
            return new Rabbit(this);
        }

        @Override
        public Builder sex(String sex) {
            this.sex = sex;
            return this;
        }

        @Override
        public Builder name(String name) {
            this.name = name;
            return this;
        }
    }
}

儿童班

子类Builder必须实现相同的接口(具有不同的泛型类型):

public static class LopBuilder implements RabbitBuilder<LopBuilder>

在子类Builder内引用父Builder的字段:

private Rabbit.Builder baseBuilder;

这确保在子代中调用父Builder方法,但是它们的实现是不同的:

@Override
public LopBuilder sex(String sex) {
    baseBuilder.sex(sex);
    return this;
}

@Override
public LopBuilder name(String name) {
    baseBuilder.name(name);
    return this;
}

public Rabbit build() {
    return new Lop(this);
}

Builder的构造函数:

public LopBuilder() {
    baseBuilder = new Rabbit.Builder();
}

构建子类的构造函数:

public Lop(LopBuilder builder) {
    super(builder.baseBuilder);
}

答案 2 :(得分:1)

这种形式似乎几乎可行。它不是很整洁,但看起来它可以避免你的问题:

class Rabbit<B extends Rabbit.Builder<B>> {

    String name;

    public Rabbit(Builder<B> builder) {
        this.name = builder.colour;
    }

    public static class Builder<B extends Rabbit.Builder<B>> {

        protected String colour;

        public B colour(String colour) {
            this.colour = colour;
            return (B)this;
        }

        public Rabbit<B> build () {
            return new Rabbit<>(this);
        }
    }
}

class Lop<B extends Lop.Builder<B>> extends Rabbit<B> {

    float earLength;

    public Lop(Builder<B> builder) {
        super(builder);
        this.earLength = builder.earLength;
    }

    public static class Builder<B extends Lop.Builder<B>> extends Rabbit.Builder<B> {

        protected float earLength;

        public B earLength(float earLength) {
            this.earLength = earLength;
            return (B)this;
        }

        @Override
        public Lop<B> build () {
            return new Lop<>(this);
        }
    }
}

public class Test {

    public void test() {
        Rabbit rabbit = new Rabbit.Builder<>().colour("White").build();
        Lop lop1 = new Lop.Builder<>().earLength(1.4F).colour("Brown").build();
        Lop lop2 = new Lop.Builder<>().colour("Brown").earLength(1.4F).build();
        //Lop.Builder<Lop, Lop.Builder> builder = new Lop.Builder<>();
    }

    public static void main(String args[]) {
        try {
            new Test().test();
        } catch (Throwable t) {
            t.printStackTrace(System.err);
        }
    }
}

虽然我已经成功构建了RabbitLop(两种形式)但我现在无法解决如何使用它的完整类型实际实例化其中一个Builder对象。< / p>

此方法的本质依赖于(B)方法中对Builder的强制转换。这允许您定义对象的类型和Builder的类型,并在构造对象时将其保留在对象中。

如果有人能够找到正确的语法(这是错误的),我将不胜感激。

Lop.Builder<Lop.Builder> builder = new Lop.Builder<>();

答案 3 :(得分:0)

由于你不能使用泛型,现在可能主要的任务是以某种方式放松输入。 我不知道你之后如何处理这些属性,但如果你使用HashMap将它们存储为键值对怎么办?因此构建器中只有一个set(key,value)包装器方法(或者可能不再需要构建器)。

在处理存储的数据时,缺点是其他类型的铸件。

如果这种情况太松,那么你可以保留现有的属性,但是有一个通用的set方法,它使用反射并根据'key'名称搜索setter方法。虽然我认为反思会有点矫枉过正。

答案 4 :(得分:0)

我做了一些实验,我发现这对我来说非常好。 请注意,我更喜欢在开始时创建实际实例,并调用该实例上的所有setter。这只是一种偏好。

与接受的答案的主要区别在于

  1. 我传递了一个表示返回类型的参数
  2. 不需要抽象......和最终构建者。
  3. 我创建了一个&new 39Builder&#39;便利方法。
  4. 代码:

    public class MySuper {
        private int superProperty;
    
        public MySuper() { }
    
        public void setSuperProperty(int superProperty) {
            this.superProperty = superProperty;
        }
    
        public static SuperBuilder<? extends MySuper, ? extends SuperBuilder> newBuilder() {
            return new SuperBuilder<>(new MySuper());
        }
    
        public static class SuperBuilder<R extends MySuper, B extends SuperBuilder<R, B>> {
            private final R mySuper;
    
            public SuperBuilder(R mySuper) {
                this.mySuper = mySuper;
            }
    
            public B withSuper(int value) {
                mySuper.setSuperProperty(value);
                return (B) this;
            }
    
            public R build() {
                return mySuper;
            }
        }
    }
    

    然后子类看起来像这样:

    public class MySub extends MySuper {
        int subProperty;
    
        public MySub() {
        }
    
        public void setSubProperty(int subProperty) {
            this.subProperty = subProperty;
        }
    
        public static SubBuilder<? extends MySub, ? extends SubBuilder> newBuilder() {
            return new SubBuilder(new MySub());
        }
    
        public static class SubBuilder<R extends MySub, B extends SubBuilder<R, B>>
            extends SuperBuilder<R, B> {
    
            private final R mySub;
    
            public SubBuilder(R mySub) {
                super(mySub);
                this.mySub = mySub;
            }
    
            public B withSub(int value) {
                mySub.setSubProperty(value);
                return (B) this;
            }
        }
    }
    

    和子库类

    public class MySubSub extends MySub {
        private int subSubProperty;
    
        public MySubSub() {
        }
    
        public void setSubSubProperty(int subProperty) {
            this.subSubProperty = subProperty;
        }
    
        public static SubSubBuilder<? extends MySubSub, ? extends SubSubBuilder> newBuilder() {
            return new SubSubBuilder<>(new MySubSub());
        }
    
        public static class SubSubBuilder<R extends MySubSub, B extends SubSubBuilder<R, B>>
            extends SubBuilder<R, B> {
    
            private final R mySubSub;
    
            public SubSubBuilder(R mySub) {
                super(mySub);
                this.mySubSub = mySub;
            }
    
            public B withSubSub(int value) {
                mySubSub.setSubSubProperty(value);
                return (B)this;
            }
        }
    
    }
    

    为了验证它是否完全正常,我使用了这个测试:

    MySubSub subSub = MySubSub
            .newBuilder()
            .withSuper (1)
            .withSub   (2)
            .withSubSub(3)
            .withSub   (2)
            .withSuper (1)
            .withSubSub(3)
            .withSuper (1)
            .withSub   (2)
            .build();
    

答案 5 :(得分:0)

最简单的解决方法是简单地重写父类的setter方法。

您避免使用泛型,易于使用,扩展和理解,并且在调用super.setter时也避免了代码重复。

public class Lop extends Rabbit {

    public final float earLength;
    public final String furColour;

    public Lop(final LopBuilder builder) {
        super(builder);
        this.earLength = builder.earLength;
        this.furColour = builder.furColour;
    }

    public static class LopBuilder extends Rabbit.Builder {

        protected float earLength;
        protected String furColour;

        public LopBuilder() {}

        @Override
        public LopBuilder sex(final String sex) {
            super.sex(sex);
            return this;
        }

        @Override
        public LopBuilder name(final String name) {
            super.name(name);
            return this;
        }

        public LopBuilder earLength(final float length) {
            this.earLength = length;
            return this;
        }

        public LopBuilder furColour(final String colour) {
            this.furColour = colour;
            return this;
        }

        @Override
        public Lop build() {
            return new Lop(this);
        }
    }
}

答案 6 :(得分:0)

面对相同的问题,我使用了emcmanus在https://community.oracle.com/blogs/emcmanus/2010/10/24/using-builder-pattern-subclasses

上提出的解决方案

我只是在这里复制他/她的首选解决方案。假设我们有两个类,ShapeRectangleRectangle继承自Shape

公共类Shape {

private final double opacity;

public double getOpacity() {
    return opacity;
}

protected static abstract class Init<T extends Init<T>> {
    private double opacity;

    protected abstract T self();

    public T opacity(double opacity) {
        this.opacity = opacity;
        return self();
    }

    public Shape build() {
        return new Shape(this);
    }
}

public static class Builder extends Init<Builder> {
    @Override
    protected Builder self() {
        return this;
    }
}

protected Shape(Init<?> init) {
    this.opacity = init.opacity;
}

}

有一个Init内部类,它是抽象的,还有Builder内部类,这是一个实际的实现。在实施Rectangle时有用:

公共类Rectangle扩展Shape {     私人决赛双倍身高;

public double getHeight() {
    return height;
}

protected static abstract class Init<T extends Init<T>> extends Shape.Init<T> {
    private double height;

    public T height(double height) {
        this.height = height;
        return self();
    }

    public Rectangle build() {
        return new Rectangle(this);
    }
}

public static class Builder extends Init<Builder> {
    @Override
    protected Builder self() {
        return this;
    }
}

protected Rectangle(Init<?> init) {
    super(init);
    this.height = init.height;
}

}

要实例化Rectangle

new Rectangle.Builder().opacity(1.0D).height(1.0D).build();

同样,从Init继承的抽象Shape.Init类和实际实现的Build。每个Builder类都实现self方法,该方法负责返回自身的正确转换版本。

Shape.Init <-- Shape.Builder
     ^
     |
     |
Rectangle.Init <-- Rectangle.Builder

答案 7 :(得分:0)

以下IEEE会议文稿Refined Fluent Builder in Java为该问题提供了全面的解决方案。

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