如何定义构建器模式层次结构,可以按任何顺序调用setter

时间:2018-01-24 18:56:07

标签: java generics abstract-class builder

考虑带有抽象构建器的抽象Data类:

abstract class Data {

    abstract static class Builder<T extends Data> {

        private String one;

        protected Builder() {
            this.one = null;
        }

        public final Builder<T> withOne(final String value) {
            this.one = value;
            return this;
        }

        protected abstract T build();
    }

    private final String one;

    protected Data(final Builder<? extends Data> builder) {
        this.one = builder.one;
    }

    public final String getOne() {
        return this.one;
    }
}

该类已扩展,其中还包括自己的扩展构建器:

public final class Extension extends Data {

    public static final class ExtensionBuilder extends Data.Builder<Extension> {

        private String two;

        private ExtensionBuilder() {
            super();
            this.two = null;
        }

        public static final ExtensionBuilder newInstance() {
            return new ExtensionBuilder();
        }

        public final ExtensionBuilder withTwo(final String value) {
            this.two = value;
            return this;
        }

        public final Extension build() {
            return new Extension(this);
        }
    }

    private final String two;

    private Extension(final ExtensionBuilder builder) {
        super(builder);
        this.two = builder.two;
    }

    public final String getTwo() {
        return this.two;
    }
}

Extension对象从两个类中获取所有方法。但是,调用setter方法的顺序很重要。这没关系:

Extension one = Extension.ExtensionBuilder
                .newInstance()
                .withTwo("two")
                .withOne("one")
                .build();

这会产生编译错误:

Extension two = Extension.ExtensionBuilder
                .newInstance()
                .withOne("one")
                .withTwo("two")
                .build();

我知道为什么,withOne()setter将Builder对象的类型从具体类向下转换为抽象类。我想知道我需要做些什么才能解决这个问题,因此可以按任何顺序调用setter。

3 个答案:

答案 0 :(得分:4)

如果您不希望(或不能)按照this answer中的建议覆盖withOne班级中的ExtensionBuilder方法,则可以使用recursively bounded generic type你的建设者:

abstract static class Builder<T extends Data, B extends Builder<T, B>> {

    private String one;

    protected Builder() {
        this.one = null;
    }

    public final B withOne(final String value) {
        this.one = value;
        return (B) this;
    }

    protected abstract T build();
}

然后,按如下方式声明ExtensionBuilder类:

public static final class ExtensionBuilder 
    extends Data.Builder<Extension, ExtensionBuilder>

这将允许您以任何顺序使用最具体的构建器方法,因为编译器现在知道具体构建器的静态类型。

修改 正如@Radiodef in the comments所指出的,有一个替代演员,其中包括在protected abstract B getThis()类中声明Builder方法:

abstract static class Builder<T extends Data, B extends Builder<T, B>> {

    private String one;

    protected Builder() {
        this.one = null;
    }

    protected abstract B getThis();

    public final B withOne(final String value) {
        this.one = value;
        return getThis(); // no need to cast now
    }

    protected abstract T build();
}

当然,在ExtensionBuilder中,您应该将其实现为:

@Override
protected ExtensionBuilder getThis() { return this; }

以下是来源:http://www.angelikalanger.com/GenericsFAQ/FAQSections/ProgrammingIdioms.html#FAQ205

答案 1 :(得分:3)

问题是Builder的{​​{1}}方法返回withOne,而不是Builder<T>,因此编译器没有看到{{1}方法。

您需要覆盖ExtensionBuilder,因此它具有相同的功能,但会返回withTwo类型的内容。

首先,在withOne中设置ExtensionBuilder 而不是 withOne,以便您可以在final中覆盖它。

然后,覆盖它。只需调用重写的方法并返回Builder即可保留相同的功能。

ExtensionBuilder

答案 2 :(得分:3)

你正在混淆两个完全不同的东西。 Builder模式是关于将对象创建与对象属性的规范分离。您的问题根本不是,而是使用方法链作为实施策略。为了证明这一点,我观察到通过转储方法链接,当前设计可以实现您喜欢的构建器方法调用的顺序:

Extension.ExtensionBuilder builder = Extension.ExtensionBuilder.newInstance();
builder.withOne("one");
builder.withTwo("two");
Extension two = builder.build();

方法链一直存在争议。 Streams API最近给了它一些积极的思想分享,但这并不适合所有情况。其中一个问题确实与你所描述的完全一致:它与继承不能很好地协调。