有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

子类化Java Builder类的设计模式

给出this Dr Dobbs article,特别是构建器模式,我们如何处理子类化构建器的情况?举一个简化版的例子,我们希望子类化以添加转基因标签,一个简单的实现是:

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);

问题当然是NutritionFacts.Builder返回的是NutritionFacts.Builder,而不是GMOFacts.Builder,那么我们如何解决这个问题,或者是否有更好的模式可以使用

注:this answer to a similar question提供了我上面的类;我的问题是关于确保生成器调用的顺序正确的问题


共 (6) 个答案

  1. # 1 楼答案

    还有另一种根据Builder模式创建类的方法,该模式符合“优先组合而非继承”

    定义父类Builder将继承的接口:

    public interface FactsBuilder<T> {
    
        public T calories(int val);
    }
    

    NutritionFacts的实现几乎相同(除了Builder实现“FactsBuilder”接口外):

    public class NutritionFacts {
    
        private final int calories;
    
        public static class Builder implements FactsBuilder<Builder> {
            private int calories = 0;
    
            public Builder() {
            }
    
            @Override
            public Builder calories(int val) {
                return this;
            }
    
            public NutritionFacts build() {
                return new NutritionFacts(this);
            }
        }
    
        protected NutritionFacts(Builder builder) {
            calories = builder.calories;
        }
    }
    

    子类的Builder应该扩展相同的接口(不同的通用实现除外):

    public static class Builder implements FactsBuilder<Builder> {
        NutritionFacts.Builder baseBuilder;
    
        private boolean hasGMO = false;
    
        public Builder() {
            baseBuilder = new NutritionFacts.Builder();
        }
    
        public Builder GMO(boolean val) {
            hasGMO = val;
            return this;
        }
    
        public GMOFacts build() {
            return new GMOFacts(this);
        }
    
        @Override
        public Builder calories(int val) {
            baseBuilder.calories(val);
            return this;
        }
    }
    

    请注意,NutritionFacts.BuilderGMOFacts.Builder(称为baseBuilder)内的一个字段。从FactsBuilder接口实现的方法调用baseBuilder同名的方法:

    @Override
    public Builder calories(int val) {
        baseBuilder.calories(val);
        return this;
    }
    

    GMOFacts(Builder builder)的构造函数中也有很大的变化。构造函数中对父类构造函数的第一次调用应传递适当的^{

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

    GMOFacts类的完整实现:

    public class GMOFacts extends NutritionFacts {
    
        private final boolean hasGMO;
    
        public static class Builder implements FactsBuilder<Builder> {
            NutritionFacts.Builder baseBuilder;
    
            private boolean hasGMO = false;
    
            public Builder() {
            }
    
            public Builder GMO(boolean val) {
                hasGMO = val;
                return this;
            }
    
            public GMOFacts build() {
                return new GMOFacts(this);
            }
    
            @Override
            public Builder calories(int val) {
                baseBuilder.calories(val);
                return this;
            }
        }
    
        protected GMOFacts(Builder builder) {
            super(builder.baseBuilder);
            hasGMO = builder.hasGMO;
        }
    }
    
  2. # 2 楼答案

    为了记录在案,为了摆脱

    unchecked or unsafe operations warning

    对于@dimadima和@Thomas N.talk的return (T) this;语句,以下解决方案适用于某些情况

    使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); }
    }
    

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

  3. # 3 楼答案

    基于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();
    }
    
  4. # 4 楼答案

    多生成器继承的完整3级示例如下所示:

    (对于为构建器提供复制构造函数的版本,请参见下面的第二个示例)

    第一级-父级(可能是抽象的)

    import lombok.ToString;
    
    @ToString
    @SuppressWarnings("unchecked")
    public abstract class Class1 {
        protected int f1;
    
        public static class Builder<C extends Class1, B extends Builder<C, B>> {
            C obj;
    
            protected Builder(C constructedObj) {
                this.obj = constructedObj;
            }
    
            B f1(int f1) {
                obj.f1 = f1;
                return (B)this;
            }
    
            C build() {
                return obj;
            }
        }
    }
    

    二级

    import lombok.ToString;
    
    @ToString(callSuper=true)
    @SuppressWarnings("unchecked")
    public class Class2 extends Class1 {
        protected int f2;
    
        public static class Builder<C extends Class2, B extends Builder<C, B>> extends Class1.Builder<C, B> {
            public Builder() {
                this((C) new Class2());
            }
    
            protected Builder(C obj) {
                super(obj);
            }
    
            B f2(int f2) {
                obj.f2 = f2;
                return (B)this;
            }
        }
    }
    

    三级

    import lombok.ToString;
    
    @ToString(callSuper=true)
    @SuppressWarnings("unchecked")
    public class Class3 extends Class2 {
        protected int f3;
    
        public static class Builder<C extends Class3, B extends Builder<C, B>> extends Class2.Builder<C, B> {
            public Builder() {
                this((C) new Class3());
            }
    
            protected Builder(C obj) {
                super(obj);
            }
    
            B f3(int f3) {
                obj.f3 = f3;
                return (B)this;
            }
        }
    }
    

    还有一个用法示例

    public class Test {
        public static void main(String[] args) {
            Class2 b1 = new Class2.Builder<>().f1(1).f2(2).build();
            System.out.println(b1);
            Class2 b2 = new Class2.Builder<>().f2(2).f1(1).build();
            System.out.println(b2);
    
            Class3 c1 = new Class3.Builder<>().f1(1).f2(2).f3(3).build();
            System.out.println(c1);
            Class3 c2 = new Class3.Builder<>().f3(3).f1(1).f2(2).build();
            System.out.println(c2);
            Class3 c3 = new Class3.Builder<>().f3(3).f2(2).f1(1).build();
            System.out.println(c3);
            Class3 c4 = new Class3.Builder<>().f2(2).f3(3).f1(1).build();
            System.out.println(c4);
        }
    }
    


    稍长一点的版本,为构建者提供了一个复制构造函数:

    第一级-父级(可能是抽象的)

    import lombok.ToString;
    
    @ToString
    @SuppressWarnings("unchecked")
    public abstract class Class1 {
        protected int f1;
    
        public static class Builder<C extends Class1, B extends Builder<C, B>> {
            C obj;
    
            protected void setObj(C obj) {
                this.obj = obj;
            }
    
            protected void copy(C obj) {
                this.f1(obj.f1);
            }
    
            B f1(int f1) {
                obj.f1 = f1;
                return (B)this;
            }
    
            C build() {
                return obj;
            }
        }
    }
    

    二级

    import lombok.ToString;
    
    @ToString(callSuper=true)
    @SuppressWarnings("unchecked")
    public class Class2 extends Class1 {
        protected int f2;
    
        public static class Builder<C extends Class2, B extends Builder<C, B>> extends Class1.Builder<C, B> {
            public Builder() {
                setObj((C) new Class2());
            }
    
            public Builder(C obj) {
                this();
                copy(obj);
            }
    
            @Override
            protected void copy(C obj) {
                super.copy(obj);
                this.f2(obj.f2);
            }
    
            B f2(int f2) {
                obj.f2 = f2;
                return (B)this;
            }
        }
    }
    

    三级

    import lombok.ToString;
    
    @ToString(callSuper=true)
    @SuppressWarnings("unchecked")
    public class Class3 extends Class2 {
        protected int f3;
    
        public static class Builder<C extends Class3, B extends Builder<C, B>> extends Class2.Builder<C, B> {
            public Builder() {
                setObj((C) new Class3());
            }
    
            public Builder(C obj) {
                this();
                copy(obj);
            }
    
            @Override
            protected void copy(C obj) {
                super.copy(obj);
                this.f3(obj.f3);
            }
    
            B f3(int f3) {
                obj.f3 = f3;
                return (B)this;
            }
        }
    }
    

    还有一个用法示例

    public class Test {
        public static void main(String[] args) {
            Class3 c4 = new Class3.Builder<>().f2(2).f3(3).f1(1).build();
            System.out.println(c4);
    
            // Class3 builder copy
            Class3 c42 = new Class3.Builder<>(c4).f2(12).build();
            System.out.println(c42);
            Class3 c43 = new Class3.Builder<>(c42).f2(22).f1(11).build();
            System.out.println(c43);
            Class3 c44 = new Class3.Builder<>(c43).f3(13).f1(21).build();
            System.out.println(c44);
        }
    }
    
  5. # 5 楼答案

    还可以重写calories()方法,让它返回扩展生成器。之所以编译,是因为Java支持covariant return types

    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);
            }
        }
        [...]
    }
    
  6. # 6 楼答案

    你可以用泛型来解决它。我想这叫做"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;
        }
    }