首页 文章

在java中,您可以将构建器模式与必需和可重新分配的字段一起使用吗?

提问于
浏览
2

这与以下问题有关:

How to improve the builder pattern?

我很好奇是否可以实现具有以下属性的构建器:

  • 部分或全部参数是必需的

  • 没有方法接收许多参数(即,没有提供给初始构建器工厂方法的默认值列表)

  • 可以重新分配所有构建器字段任意次

  • 编译器应检查是否已设置所有参数

  • 可以要求参数最初按某种顺序设置,但是一旦设置了任何参数,所有后续构建器都可以再次设置此参数(即,您可以重新分配所需构建器的任何字段的值)

  • 对于setter不存在重复的代码(例如,构建器子类型中没有重写的setter方法)

下面是一个失败的尝试(省略了空的私有构造函数) . 考虑以下玩具构建器实现,并注意带有“Foo f2”的行具有编译器错误,因为a的继承setter返回BuilderB,而不是BuilderFinal . 有没有办法使用java类型系统来参数化setter的返回类型以实现上述目标,或者以其他方式实现它们 .

public final class Foo {

    public final int a;
    public final int b;
    public final int c;

    private Foo(
            int a,
            int b,
            int c) {
        this.a = a;
        this.b = b;
        this.c = c;
    }

    public static BuilderA newBuilder() {
        return new BuilderC();
    }

    public static class BuilderA {
        private volatile int a;

        public BuilderB a(int v) {
            a = v;
            return (BuilderB) this;
        }

        public int a() {
            return a;
        }
    }

    public static class BuilderB extends BuilderA {
        private volatile int b;

        public BuilderC b(int v) {
            b = v;
            return (BuilderC) this;
        }

        public int b() {
            return b;
        }
    }

    public static class BuilderC extends BuilderB {
        private volatile int c;

        public BuilderFinal c(int v) {
            c = v;
            return (BuilderFinal) this;
        }

        public int c() {
            return c;
        }
    }

    public static class BuilderFinal extends BuilderC {

        public Foo build() {
            return new Foo(
                    a(),
                    b(),
                    c());
        }
    }

    public static void main(String[] args) {
        Foo f1 = newBuilder().a(1).b(2).c(3).build();
        Foo f2 = newBuilder().a(1).b(2).c(3).a(4).build();
    }

}

5 回答

  • 1

    您的要求非常难,但请查看此通用解决方案是否符合要求:

    public final class Foo {
    
        public final int a;
        public final int b;
        public final int c;
    
        private Foo(
                int a,
                int b,
                int c) {
            this.a = a;
            this.b = b;
            this.c = c;
        }
    
        public static BuilderA<? extends BuilderB<?>> newBuilder() {
            return new BuilderFinal();
        }
    
        public static class BuilderA<T extends BuilderB<?>> {
            private volatile int a;
    
            @SuppressWarnings("unchecked")
            public T a(int v) {
                a = v;
                return (T) this;
            }
    
            public int a() {
                return a;
            }
        }
    
        public static class BuilderB<T extends BuilderC<?>> extends BuilderA<T> {
            private volatile int b;
    
            @SuppressWarnings("unchecked")
            public T b(int v) {
                b = v;
                return (T) this;
            }
    
            public int b() {
                return b;
            }
        }
    
        public static class BuilderC<T extends BuilderFinal> extends BuilderB<T> {
            private volatile int c;
    
            @SuppressWarnings("unchecked")
            public T c(int v) {
                c = v;
                return (T) this;
            }
    
            public int c() {
                return c;
            }
        }
    
        public static class BuilderFinal extends BuilderC<BuilderFinal> {
    
            public Foo build() {
                return new Foo(
                        a(),
                        b(),
                        c());
            }
        }
    
        public static void main(String[] args) {
            Foo f1 = newBuilder().a(1).b(2).c(3).build();
            Foo f2 = newBuilder().a(1).b(2).c(3).a(4).build();
        }
    
    }
    
  • -1

    据我所知,应该使用构建器模式,以防使用多个参数,这使得调用相当复杂,因为参数可能会交换位置或者没有明确说明哪个参数用于什么 .

    一个经验法则是要求构建器的构造函数中的必需参数和方法中的可选参数 . 然而,通常可能需要超过4个参数,这使得再次调用相当不清楚并且模式是多余的 . 因此,也可以使用拆分为默认构造函数和每个参数的参数设置 .

    检查应该在自己的方法中进行,该方法在构建方法中调用,因此您可以使用 super 调用它 . 编译时安全性仅在正确的数据类型上得到保证(只有异常 - 可以为null,必须在 checkParameters() -method中获取) . 但是,您可以强制在Builder构造函数中设置所有必需参数,但如前所述,这可能会导致冗余模式 .

    import java.util.ArrayList;
    import java.util.List;
    
    public class C
    {
        public static class Builder<T extends C, B extends C.Builder<? extends C,? extends B>> extends AbstractBuilder<C>
        {
            protected String comp1;
            protected String comp2;
            protected int comp3;
            protected int comp4;
            protected int comp5;
            protected List<Object> comp6 = new ArrayList<>();
            protected String optional1;
            protected List<Object> 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();
        }
    }
    

    在从C和构建器扩展D时,您需要覆盖 checkParameters()build() 方法 . 由于使用泛型,正确的类型将在调用时返回 build()

    import java.util.List;
    
    public class D extends C
    {
        public static class Builder<T extends D, B extends D.Builder<? extends D, ? extends B>> extends C.Builder<D, Builder<D, B>>
        {
            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();
        }
    }
    

    抽象构建器类非常简单:

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

    例外也很简单:

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

    最后但并非最不重要的主要方法:

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

    输出:

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

    虽然,在使用Generics之前,我建议坚持使用KISS策略并忘记构建器的继承并将它们编码为简单和愚蠢(其中一部分包括哑复制和粘贴)


    @edit:好的,在完成所有的工作并重新阅读OP以及链接的帖子后,我对这些要求做了完全错误的假设 - 就像德国的措辞所说:“手术成功,病人已经死了” - 尽管我离开了这篇文章,以防万一有人想要一个复制和粘贴解决方案的构建器继承实际上返回正确的类型而不是基类型

  • 2

    我有一次a crazy idea,它有点违背你的一些要求,但我认为你可以让构建器构造函数获取所需的参数,但是这样可以清楚地知道正在设置哪些参数 . 看一看:

    package myapp;
    
    public final class Foo {
    
      public final int a;
      public final int b;
      public final int c;
    
      private Foo(int a, int b, int c) {
          this.a = a;
          this.b = b;
          this.c = c;
      }
    
      public static class Builder {
        private int a;
        private int b;
        private int c;
    
        public Builder(A a, B b, C c) {
          this.a = a.v;
          this.b = b.v;
          this.c = c.v;
        }
    
        public Builder a(int v) { a = v; return this; }
        public Builder b(int v) { b = v; return this; }
        public Builder c(int v) { c = v; return this; }
    
        public Foo build() {
          return new Foo(a, b, c);
        }
      }
    
      private static class V {
        int v;
        V(int v) { this.v = v; }
      }
      public static class A extends V { A(int v) { super(v); } }
      public static class B extends V { B(int v) { super(v); } }
      public static class C extends V { C(int v) { super(v); } }
      public static A a(int v) { return new A(v); }
      public static B b(int v) { return new B(v); }
      public static C c(int v) { return new C(v); }
    
      public static void main(String[] args) {
        Foo f1 = new Builder(a(1), b(2), c(3)).build();
        Foo f2 = new Builder(a(1), b(2), c(3)).a(4).build();
      }
    
    }
    

    对于其他客户,静态导入是您的朋友:

    package myotherapp;
    
    import myapp.Foo;
    import static myapp.Foo.*;
    
    public class Program {
    
      public static void main(String[] args) {
        Foo f1 = new Builder(a(1), b(2), c(3)).build();
        Foo f2 = new Builder(a(1), b(2), c(3)).a(4).build();
      }
    
    }
    
  • 3

    基于Jordão的想法,我提出了以下内容,即使类型参数中存在一些重复的代码,也可以满足所有要求1-6 . 本质上,我们的想法是通过使用类型参数覆盖继承方法的返回值来“传递”每个方法的返回类型 . 即使代码是冗长的和不切实际的,而实际上需要欧米茄(N ^ 3)字符,如果你出去扩展它来场n的任意数,我张贴,因为我认为这是一个有趣的使用Java类型系统 . 如果有人能找到减少类型参数数量的方法(特别是渐近),请在评论中发帖或写下另一个答案 .

    public final class Foo {
    
        public final int a;
        public final int b;
        public final int c;
    
        private Foo(
                int a,
                int b,
                int c) {
            this.a = a;
            this.b = b;
            this.c = c;
        }
    
        public static BuilderA<? extends BuilderB<?, ?>, ? extends BuilderC<?, ?>> newBuilder() {
            return new BuilderFinal();
        }
    
        public static class BuilderA<B extends BuilderB<?, ?>, C extends BuilderC<?, ?>> {
            private volatile int a;
    
            @SuppressWarnings("unchecked")
            public B a(int v) {
                a = v;
                return (B) this;
            }
    
            public int a() {
                return a;
            }
        }
    
        public static class BuilderB<B extends BuilderB<?, ?>, C extends BuilderC<?, ?>> extends BuilderA<B, C> {
            private volatile int b;
    
            @SuppressWarnings("unchecked")
            public C b(int v) {
                b = v;
                return (C) this;
            }
    
            public int b() {
                return b;
            }
        }
    
        public static class BuilderC<B extends BuilderC<?, ?>, C extends BuilderC<?, ?>> extends BuilderB<B, C> {
            private volatile int c;
    
            @SuppressWarnings("unchecked")
            public BuilderFinal c(int v) {
                c = v;
                return (BuilderFinal) this;
            }
    
            public int c() {
                return c;
            }
        }
    
        public static class BuilderFinal extends BuilderC<BuilderFinal, BuilderFinal> {
    
            public Foo build() {
                return new Foo(
                        a(),
                        b(),
                        c());
            }
        }
    
        public static void main(String[] args) {
            Foo f1 = newBuilder().a(1).b(2).c(3).a(2).build();
            Foo f2 = newBuilder().a(1).a(2).c(3).build(); // compile error
            Foo f3 = newBuilder().a(1).b(2).a(3).b(4).b(5).build(); // compile error
        }
    
    }
    
  • 0

    为什么不想覆盖BuilderFinal中的setter?他们只需要转发超级方法:

    public static class BuilderFinal extends BuilderC {
    
        @Override
        public BuilderFinal a(int v) {
            return (BuilderFinal) super.a(v);
        }
    
        @Override
        public BuilderFinal b(int v) {
            return (BuilderFinal) super.b(v);
        }
    
        public Foo build() {
            return new Foo(
                    a(),
                    b(),
                    c());
        }
    }
    

相关问题