构建器模式和继承

Eri*_*ric 49 java inheritance design-patterns 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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在我们已经有了一些代码继续进行,我希望构建一个Lop:

Lop lop = new Lop.LopBuilder().furColour("Gray").name("Rabbit").earLength(4.6f);
Run Code Online (Sandbox Code Playgroud)

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

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

我目前解决的一种方法是覆盖Builder层次结构中超类的所有方法,并简单地执行以下操作:

public ConcreteBuilder someOverridenMethod(Object someParameter)
{
    super(someParameter);
    return this;
}
Run Code Online (Sandbox Code Playgroud)

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

我不知道有什么问题可以解决我的问题吗?优选地,解决方案与设计模式一致.谢谢!

Rad*_*def 52

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

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

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

class MammalBuilder<B extends MammalBuilder<B>> {
    ...
}
class RabbitBuilder<B extends RabbitBuilder<B>>
        extends MammalBuilder<B> {
    ...
}
Run Code Online (Sandbox Code Playgroud)

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

static RabbitBuilder<?> builder() {
    return new RabbitBuilder<>();
}
Run Code Online (Sandbox Code Playgroud)

这是有效的,因为类型变量的绑定确保所有方法RabbitBuilder都具有返回类型RabbitBuilder,即使类型参数只是通配符.

不过,我并不喜欢它,因为你需要在任何地方都使用通配符,而且你只能使用菱形或原始类型创建一个新实例.我想你最终会有一点尴尬.


顺便说一句,关于这个:

@SuppressWarnings("unchecked")
final B self() {
    return (B) this;
}
Run Code Online (Sandbox Code Playgroud)

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

abstract B self();
Run Code Online (Sandbox Code Playgroud)

然后在叶子类中覆盖它:

@Override
RabbitBuilder self() { return this; }
Run Code Online (Sandbox Code Playgroud)

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

  • 额外的类有点啰嗦,但如上所示,您可以通过将'build'方法仅放在具体类上来将其用作组织.将构建方法放在泛型类上要么意味着你有许多方法无用地构建超类或者第二个复杂的泛型系统.至于自然延伸,不知道该说些什么,这可能是最优雅的延伸.您遇到的是该模式的合法问题. (3认同)

jmg*_*net 6

面对同样的问题,我使用了 emcmanus 提出的解决方案:https://community.oracle.com/blogs/emcmanus/2010/10/24/using-builder-pattern-subclasses

我只是在这里重新复制他/她的首选解决方案。假设我们有两个班级,Shape并且RectangleRectangle继承自Shape.

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

Init内部类(抽象)和Builder内部类(实际实现)。在实施以下内容时将很有用Rectangle

public class Rectangle extends Shape {
    private final double height;

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

实例化Rectangle

new Rectangle.Builder().opacity(1.0D).height(1.0D).build();
Run Code Online (Sandbox Code Playgroud)

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

Shape.Init <-- Shape.Builder
     ^
     |
     |
Rectangle.Init <-- Rectangle.Builder
Run Code Online (Sandbox Code Playgroud)