In java, can you use the builder pattern with required and reassignable fields?

后端 未结 5 692
南笙
南笙 2021-01-20 01:57

This is related to the following question:

How to improve the builder pattern?

I\'m curious whether it\'s possible to implement a builder with the following

5条回答
  •  鱼传尺愫
    2021-01-20 02:09

    To my knowledge the builder pattern should be used in case multiple parameters are used that make the invocation rather complicated as parameters might swap positions or not make it obviously clear what which parameter is for.

    A rule of thumb would be to require compulsory parameters within the constructor of the builder and optional parameters within the methods. However, often more than 4 parameters may be required which makes the invocation again rather unclear and the pattern redundant. So a split up into default constructor and parameter setting for each parameter can also be used.

    The checks should happen in a own method which is invoked within the build-method so you could invoke it using super. Compile-time security is only guaranteed on the correct data types (only exception - null is possible to, this has to be fetched within the checkParameters()-method). You can however force that all required parameters are set on requiring them within the Builder constructor - but as mentioned before, this may lead to a redundant pattern.

    import java.util.ArrayList;
    import java.util.List;
    
    public class C
    {
        public static class Builder> extends AbstractBuilder
        {
            protected String comp1;
            protected String comp2;
            protected int comp3;
            protected int comp4;
            protected int comp5;
            protected List comp6 = new ArrayList<>();
            protected String optional1;
            protected List optional2 = new ArrayList<>();
    
            public Builder()
            {
    
            }
    
            public B withComp1(String comp1)
            {
                this.comp1 = comp1;
                return (B)this;
            }
    
            public B withComp2(String comp2)
            {
                this.comp2 = comp2;
                return (B)this;
            }
    
            public B withComp3(int comp3)
            {
                this.comp3 = comp3;
                return (B)this;
            }
    
            public B withComp4(int comp4)
            {
                this.comp4 = comp4;
                return (B)this;
            }
    
            public B withComp5(int comp5)
            {
                this.comp5 = comp5;
                return (B)this;
            }
    
            public B withComp6(Object comp6)
            {
                this.comp6.add(comp6);
                return (B)this;
            }
    
            public B withOptional1(String optional1)
            {
                this.optional1 = optional1;
                return (B)this;
            }
    
            public B withOptional2(Object optional2)
            {
                this.optional2.add(optional2);
                return (B)this;
            }
    
            @Override
            protected void checkParameters() throws BuildException
            {
                if (this.comp1 == null)
                    throw new BuildException("Comp1 violates the rules");
                if (this.comp2 == null)
                    throw new BuildException("Comp2 violates the rules");
                if (this.comp3 == 0)
                    throw new BuildException("Comp3 violates the rules");
                if (this.comp4 == 0)
                    throw new BuildException("Comp4 violates the rules");
                if (this.comp5 == 0)
                    throw new BuildException("Comp5 violates the rules");
                if (this.comp6 == null)
                    throw new BuildException("Comp6 violates the rules");
            }
    
            @Override
            public T build() throws BuildException
            {
                this.checkParameters();
    
                C c = new C(this.comp1, this.comp2,this.comp3, this.comp4, this.comp5, this.comp6);
                c.setOptional1(this.optional1);
                c.setOptional2(this.optional2);
                return (T)c;
            }
        }
    
        private final String comp1;
        private final String comp2;
        private final int comp3;
        private final int comp4;
        private final int comp5;
        private final List comp6;
        private String optional1;
        private List optional2;
    
        protected C(String comp1, String comp2, int comp3, int comp4, int comp5, List comp6)
        {
            this.comp1 = comp1;
            this.comp2 = comp2;
            this.comp3 = comp3;
            this.comp4 = comp4;
            this.comp5 = comp5;
            this.comp6 = comp6;
        }
    
        public void setOptional1(String optional1)
        {
            this.optional1 = optional1;
        }
    
        public void setOptional2(List optional2)
        {
            this.optional2 = optional2;
        }
    
        // further methods omitted
    
        @Override
        public String toString()
        {
            StringBuilder sb = new StringBuilder();
            sb.append(this.comp1);
            sb.append(", ");
            sb.append(this.comp2);
            sb.append(", ");
            sb.append(this.comp3);
            sb.append(", ");
            sb.append(this.comp4);
            sb.append(", ");
            sb.append(this.comp5);
            sb.append(", ");
            sb.append(this.comp6);
    
            return sb.toString();
        }
    }
    
    
    

    On extending D from C and also the builder, you need to override the checkParameters() and build() method. Due to the use of Generics the correct type will be return on invoking build()

    import java.util.List;
    
    public class D extends C
    {
        public static class Builder> extends C.Builder>
        {
            protected String comp7;
    
            public Builder()
            {
    
            }
    
            public B withComp7(String comp7)
            {
                this.comp7 = comp7;
                return (B)this;
            }
    
            @Override
            public void checkParameters() throws BuildException
            {
                super.checkParameters();
    
                if (comp7 == null)
                    throw new BuildException("Comp7 violates the rules");
            }
    
            @Override
            public T build() throws BuildException
            {
                this.checkParameters();
    
                D d = new D(this.comp1, this.comp2, this.comp3, this.comp4, this.comp5, this.comp6, this.comp7);
    
                if (this.optional1 != null)
                    d.setOptional1(optional1);
                if (this.optional2 != null)
                    d.setOptional2(optional2);
    
                return (T)d;
            }
        }
    
        protected String comp7;
    
        protected D(String comp1, String comp2, int comp3, int comp4, int comp5, List comp6, String comp7)
        {
            super(comp1, comp2, comp3, comp4, comp5, comp6);
            this.comp7 = comp7;
        }
    
        @Override
        public String toString()
        {
            StringBuilder sb = new StringBuilder();
            sb.append(super.toString());
            sb.append(", ");
            sb.append(this.comp7);
            return sb.toString();
        }
    }
    

    The abstract builder class is quite simple:

    public abstract class AbstractBuilder
    {
        protected abstract void checkParameters() throws BuildException;
    
        public abstract  T build() throws BuildException;
    }
    

    The exception is simple too:

    public class BuildException extends Exception
    {
        public BuildException(String msg)
        {
            super(msg);
        }
    }
    

    And last but not least the main method:

    public static void main(String ... args)
    {
        try
        {
            C c = new C.Builder<>().withComp1("a1").withComp2("a2").withComp3(1)
                .withComp4(4).withComp5(5).withComp6("lala").build();
            System.out.println("c: " + c);
    
            D d = new D.Builder<>().withComp1("d1").withComp2("d2").withComp3(3)
                .withComp4(4).withComp5(5).withComp6("lala").withComp7("d7").build();
            System.out.println("d: " + d);
    
            C c2 = new C.Builder<>().withComp1("a1").withComp3(1)
                .withComp4(4).withComp5(5).withComp6("lala").build();
            System.out.println(c2);
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }
    

    Output:

    c: a1, a2, 1, 4, 5, [lala]
    d: d1, d2, 3, 4, 5, [lala], d7
    Builders.BuildException: Comp2 violates the rules
            ... // StackTrace omitted
    

    Though, before messing to much with Generics I'd suggest to stick to the KISS policy and forget inheritance for builders and code them simple and stupid (with part of them including dumb copy&paste)


    @edit: OK, after all the work done and re-reading the OP as well as the linked post I had a totally wrong assumption of the requirements - like a German wording says: "Operation successful, patient is dead" - though I leave this post here in case someone wants a copy&paste like solution for a builder-inheritance which actually returns the correct type instead of the the base type

    提交回复
    热议问题