问题

Givethis Dr Dobbs article,特别是Builder Pattern,我们如何处理子类化Builder的情况?在我们想要子类化以添加GMO标签的示例的简化版本中,一个天真的实现将是:

public class NutritionFacts {                                                                                                    

    private final int calories;                                                                                                  

    public static class Builder {                                                                                                
        private int calories = 0;                                                                                                

        public Builder() {}                                                                                                      

        public Builder calories(int val) { calories = val; return this; }                                                                                                                        

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

    protected NutritionFacts(Builder builder) {                                                                                  
        calories = builder.calories;                                                                                             
    }                                                                                                                            
}

子类:

public class GMOFacts extends NutritionFacts {                                                                                   

    private final boolean hasGMO;                                                                                                

    public static class Builder extends NutritionFacts.Builder {                                                                 

        private boolean hasGMO = false;                                                                                          

        public Builder() {}                                                                                                      

        public Builder GMO(boolean val) { hasGMO = val; return this; }                                                           

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

    protected GMOFacts(Builder builder) {                                                                                        
        super(builder);                                                                                                          
        hasGMO = builder.hasGMO;                                                                                                 
    }                                                                                                                            
}

现在,我们可以编写如下代码:

GMOFacts.Builder b = new GMOFacts.Builder();
b.GMO(true).calories(100);

但是,如果我们得到错误的订单,那一切都会失败:

GMOFacts.Builder b = new GMOFacts.Builder();
b.calories(100).GMO(true);

问题当然是5824235783返回aNutritionFacts.Builder,而不是aGMOFacts.Builder,那么我们如何解决这个问题,还是有更好的模式使用?

注意:this answer to a similar question提供我上面的课程;我的问题是关于确保构建器调用的顺序正确的问题。


#1 热门回答(136 赞)

你可以使用泛型来解决它。我认为这被称为the"Curiously recurring generic patterns"

使基类构建器方法的返回类型成为通用参数。

public class NutritionFacts {

    private final int calories;

    public static class Builder<T extends Builder<T>> {

        private int calories = 0;

        public Builder() {}

        public T calories(int val) {
            calories = val;
            return (T) this;
        }

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

    protected NutritionFacts(Builder<?> builder) {
        calories = builder.calories;
    }
}

现在使用派生类构建器作为泛型参数来实例化基础构建器。

public class GMOFacts extends NutritionFacts {

    private final boolean hasGMO;

    public static class Builder extends NutritionFacts.Builder<Builder> {

        private boolean hasGMO = false;

        public Builder() {}

        public Builder GMO(boolean val) {
            hasGMO = val;
            return this;
        }

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

    protected GMOFacts(Builder builder) {
        super(builder);
        hasGMO = builder.hasGMO;
    }
}

#2 热门回答(29 赞)

只是为了记录,摆脱了

未经检查或不安全的操作警告

对于return (T) this;声明为@dimadima和@Thomas N.谈论,以下解决方案适用于某些情况。

Makeabstract声明泛型类型的构建器(本例中为T extends Builder)并声明如下的protected abstract T getThis()abstract方法:

public abstract static class Builder<T extends Builder<T>> {

    private int calories = 0;

    public Builder() {}

    /**The solution for the unchecked cast warning. */
    public abstract T getThis();

    public T calories(int val) {
        calories = val;

        // no cast needed
        return getThis();
    }

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

更多详细信息,请参阅http://www.angelikalanger.com/GenericsFAQ/FAQSections/ProgrammingIdioms.html#FAQ205


#3 热门回答(17 赞)

基于a blog post,这种方法要求所有非叶类都是抽象的,并且所有叶类必须是最终的。

public abstract class TopLevel {
    protected int foo;
    protected TopLevel() {
    }
    protected static abstract class Builder
        <T extends TopLevel, B extends Builder<T, B>> {
        protected T object;
        protected B thisObject;
        protected abstract T createObject();
        protected abstract B thisObject();
        public Builder() {
            object = createObject();
            thisObject = thisObject();
        }
        public B foo(int foo) {
            object.foo = foo;
            return thisObject;
        }
        public T build() {
            return object;
        }
    }
}

然后,你有一些扩展此类及其构建器的中间类,以及你需要的更多内容:

public abstract class SecondLevel extends TopLevel {
    protected int bar;
    protected static abstract class Builder
        <T extends SecondLevel, B extends Builder<T, B>> extends TopLevel.Builder<T, B> {
        public B bar(int bar) {
            object.bar = bar;
            return thisObject;
        }
    }
}

最后,一个具体的叶类,可以按任何顺序调用任何父类的所有构建器方法:

public final class LeafClass extends SecondLevel {
    private int baz;
    public static final class Builder extends SecondLevel.Builder<LeafClass,Builder> {
        protected LeafClass createObject() {
            return new LeafClass();
        }
        protected Builder thisObject() {
            return this;
        }
        public Builder baz(int baz) {
            object.baz = baz;
            return thisObject;
        }
    }
}

然后,你可以从层次结构中的任何类调用任何顺序的方法:

public class Demo {
    LeafClass leaf = new LeafClass.Builder().baz(2).foo(1).bar(3).build();
}

原文链接