子类化Java Builder类

Ken*_*Y-N 123 java design-patterns

这篇Dr Dobbs文章,特别是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;                                                                                             
    }                                                                                                                            
}
Run Code Online (Sandbox Code Playgroud)

子类:

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

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

GMOFacts.Builder b = new GMOFacts.Builder();
b.GMO(true).calories(100);
Run Code Online (Sandbox Code Playgroud)

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

GMOFacts.Builder b = new GMOFacts.Builder();
b.calories(100).GMO(true);
Run Code Online (Sandbox Code Playgroud)

问题当然是NutritionFacts.Builder返回a NutritionFacts.Builder而不是a GMOFacts.Builder,所以我们如何解决这个问题,还是有更好的模式可以使用?

注意:这个类似问题的答案提供了我上面的课程; 我的问题是关于确保构建器调用的顺序正确的问题.

gka*_*mal 158

您可以使用泛型来解决它.我认为这被称为"奇怪的反复出现的通用模式"

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

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

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

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

  • @gkamal`return(T)this;`导致`unchecked或unsafe operations`警告.这是不可避免的,对吗? (9认同)
  • 请注意,`Builder <T extends Builder>`实际上是一个**rawtype** - 这应该是`Builder <T extends Builder <T >>`. (8认同)
  • 要解决`unchecked cast`警告,请参阅其他答案中提出的解决方案:http://stackoverflow.com/a/34741836/3114959 (3认同)
  • 嗯,我想我必须要么 (a) 发布一个新问题,(b) 用 `implements` 而不是 `extends` 重新设计,或者 (c) 扔掉所有东西。我现在有一个奇怪的编译错误,其中 `leafBuilder.leaf().leaf()` 和 `leafBuilder.mid().leaf()` 没问题,但是 `leafBuilder.leaf().mid().leaf()`失败... (2认同)
  • @ user2957378 GMOFacts的Builder也必须是通用的。Builder&lt;B扩展了Builder &lt;B &gt;&gt;扩展了NutritionFacts.Builder &lt;Builder&gt;-并且这种模式可以根据需要继续下降。如果声明非通用生成器,则无法扩展模式。 (2认同)

Ste*_*vra 35

只是为了记录,摆脱了

unchecked or unsafe operations 警告

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

使abstract构造器声明泛型类型(T extends Builder在本例中)并声明protected abstract T getThis()抽象方法如下:

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

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

  • 这不是问题的答案,因为基类可能不是抽象的! (2认同)

Q23*_*Q23 19

基于博客文章,这种方法要求所有非叶类都是抽象的,并且所有叶类必须是最终的.

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

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

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

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

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

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

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


Fla*_*vio 6

您也可以覆盖该calories()方法,并让它返回扩展构建器.这是因为Java支持协变返回类型.

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 Builder calories(int val)
        { super.calories(val); return this; }
        public GMOFacts build() {
            return new GMOFacts(this);
        }
    }
    [...]
}
Run Code Online (Sandbox Code Playgroud)