【问题标题】:Is there a way to refactor these constructors?有没有办法重构这些构造函数?
【发布时间】:2020-05-17 18:53:52
【问题描述】:

所以我有一个对象,我们称之为 myObject

这是我的对象的构造函数

private static class myObject {
        public myObject(int argA) {
            this.argA = argA;
        }

        public myObject(int argA, boolean argB) {
            this.argA = argA;
            this.argB = argB;
        }

        public myObject(int argA, int argC, int argD) {
            this.argA = argA;
            this.argC = argC;
            this.argD = argD;
        }

        public myObject(int argA, String argE) {
            this.argA = argA;
            this.argE = argE;
        }


        public int argA = 1;
        public boolean argB;
        public int argC = 4;
        public int argD = 5;
        public String argE;

基本上我有默认值,并且构造函数会在需要时覆盖这些默认值。 当我调用这些构造函数时,这使得代码非常干净,我可以

myObject newObject = new myObject(4);

然而,一个 API 给了我一个参数列表来创建这个对象

List objectParams1 = Arrays.asList(1,3,4)
List objectParams2 = Arrays.asList(1,false)
List objectParams3 = Arrays.asList(1,"tomato")
myObject newObjectWithTheseParameters1 = ?;
myObject newObjectWithTheseParameters2 = ?;
myObject newObjectWithTheseParameters3 = ?;

使用参数列表创建此对象非常困难,因为它不知道要使用哪个构造函数。构建器方法是解决这个问题的方法吗?然而,这将使代码库变得更大,因为我必须调用这个构造函数〜 100 次..

myObject objectA = myObject.builder().withargA(4).withArgB(true).build();

【问题讨论】:

  • 顺便说一句,Java 类名以大写变量 (MyObject) 开头。否则会很混乱,尤其是在调用静态方法时
  • 在这种情况下,我非常喜欢构建器方法。是的,它更长,但是当您不必记住哪种类型组合指的是哪种参数组合时,它会更容易阅读。
  • @LouisWasserman 我认为您在这里不需要建造师。只是一个工厂方法,用于测试列表的大小和类型(以区分 int boolean/int String 情况)。
  • 我总是更喜欢可读性而不是其他任何问题。 Java 已经有很多样板代码。不确定添加更多有什么不同?您始终可以使用lombok 来最小化样板代码。 @Builder 是一个用于生成构建器的简单注解。
  • @PLfan666 我很困惑您如何能够从List objectParams1 获得对构建器的静态调用。你真的是说你得到了一个对象列表吗?如果是这样,为什么重构构造函数或使用构建器会有所帮助?

标签: java parameters constructor


【解决方案1】:

不声称这是正确的方法,但您可以在主构造函数中设置所有值,然后使用 this 关键字从使用其他签名定义的构造函数调用主构造函数:

这里唯一真正要注意的是,我设置了一些“默认”值,但在不同的构造函数中没有提供任何值。

private static class myObject {

    public int argA = 1;
    public boolean argB;
    public int argC = 4;
    public int argD = 5;
    public String argE;

    public myObject(int argA, boolean argB, int argC, int argD, String argE) {
        this.argA = argA;
        this.argB = argB;
        this.argC = argC;
        this.argD = argD;
        this.argE = argE;
    }

    public myObject(int argA) {
        this(argA, false, 0, 0, null);
    }

    public myObject(int argA, boolean argB) {
        this(argA, argB, 0, 0, null);
    }

    public myObject(int argA, int argC, int argD) {
        this(argA, false, argC,  argD, null);
    }

    public myObject(int argA, String argE) {
        this(argA, false, 0, 0, argE);
    }
}

如果您要添加大量这些类型的构造函数,最终可能会出现无法正常工作的签名冲突。当您需要指定一堆不同的可选参数 + 一些是强制性的时,Builder 是很好的选择。实现起来相对简单,因为每个方法都会返回自己 (this),您只需根据需要更新字段,然后最后调用 .build() 来创建对象。

【讨论】:

  • 但是,给定一个包含参数的 List,你如何调用这些构造函数之一?
【解决方案2】:

你只有四种情况,所以写一个静态工厂方法很容易:

static myObject create(List<?> args) {
  int argA = (int) args.get(0);
  switch (args.size()) {
    case 1:
      return new myObject(argA);
    case 2:
      if (args.get(1) instanceof Boolean) {
        return new myObject(argA, (boolean) args.get(1)) 
      }
      return new myObject(argA, (String) args.get(1));
    case 3:
        return new myObject(argA, (int) args.get(1), (int) args.get(2));
    default:
      throw new IllegalArgumentException();
  }
}

然后:

myObject newObjectWithTheseParameters1 = create(objectParams1);
// etc.

这很糟糕(如果列表中的元素数量错误,或类型错误的元素,或装箱的原始元素为空,它可能会在运行时以各种方式失败),但我没有如果参数来自列表,请真正了解您还有什么其他选择。


不做显式检查的替代方法是使用反射来获取构造函数:

Class<?>[] classes =
    args.stream()
        .map(Object::getClass)
        .map(YourClass::unboxedClass)
        .toArray(Class<?>[]::new);

其中unboxedClass 是一种将Integer.classBoolean.class 转换为int.classboolean.class 的方法。那么:

return myObject.getClass().getConstructor(classes).newInstance(args);

(并处理所有检查的异常)。

【讨论】:

    【解决方案3】:

    创建一个接受所有参数的构造函数(如果需要,将其设为私有)并从所有其他参数中调用该构造函数。您没有的参数将设置为其默认值:

    private static class MyObject {
    
        private boolean b;
        private int a, c, d;
        private String e;
    
        private MyObject(int a, boolean b, int c, int d, String e) {
            this.a = a;
            this.b = b;
            this.c = c;
            this.d = d;
            this.e = e;
        }
    
        public MyObject(int a, int c, int d) {
            this(a, false, c, d, null);
        }
    
        public MyObject(int a, boolean b) {
            this(a, b, 3, 4, null);
        }
    
        public MyObject(int a, String e) {
            this(a, false, 3, 4, e);
        }
    
    }
    

    然而,这也有一些缺点:

    1. 它会损害可读性并让您感到困惑。
    2. 如果您想更改某些参数的默认值,您必须记住在每个构造函数中更改它们。您可以通过将默认值存储在 static final 变量中来解决此问题,但仍不理想。

    您还应该考虑以不同的方式命名变量,因为 a, b, c, d and eargA, argB, argC, argD, argE 并不能真正传达太多信息。

    【讨论】:

      【解决方案4】:

      如果您愿意,可以在此处使用构建器模式。这可能看起来很丑陋和样板化,但这意味着您不需要为每种情况使用单独的构造函数,并且它将允许您链接您的构建器,因为Builder 中的每个实例方法都返回this。如果方法名称保持简短,看起来也不会太糟糕。

      您可以像这样使用它(请注意,您可以省略 c 或任何其他字段,因为默认值是在 Builder 类中设置的):

      MyObject object = 
        new Builder()
          .a(4)
          .b(true)
          .d(0)
          .e("56")
          .build();
      

      修改后的MyObject类:

      class MyObject {
        public int a;
        public boolean b;
        public int c;
        public int d;
        public String e;
      
        public MyObject(int a, boolean b, int c, int d, String e) {
          this.a = a;
          this.b = b;
          this.c = c;
          this.d = d;
          this.e = e;
        }
      }
      

      Builder

      class Builder {
        public int a = 1;
        public boolean b;
        public int c = 4;
        public int d = 5;
        public String e;
      
        public Builder a(int a) {
          this.a = a;
          return this;
        }
      
        public Builder b(boolean b) {
          this.b = b;
          return this;
        }
      
        public Builder c(int c) {
          this.c = c;
          return this;
        }
      
        public Builder d(int d) {
          this.d = d;
          return this;
        }
      
        public Builder e(String e) {
          this.e = e;
          return this;
        }
      
        public MyObject build() {
          return new MyObject(a, b, c, d, e);
        }
      }
      

      另一种实现构建器的方法,使用Map 并在之后进行强制转换。它是类型安全的,但上面带有字段的类可能更好,因为它不涉及不必要的装箱和使用原语拆箱。

      class Builder {
      
        private Map<String, Object> map = new HashMap<>();
      
        {
          map.put("a", 1);
          map.put("b", false);
          map.put("c", 4);
          map.put("d", 5);
          map.put("e", null);
        }
      
        public Builder a(int a) {
          map.put("a", a);
          return this;
        }
      
        public Builder b(boolean b) {
          map.put("b", b);
          return this;
        }
      
        public Builder c(int c) {
          map.put("c", c);
          return this;
        }
      
        public Builder d(int d) {
          map.put("d", d);
          return this;
        }
      
        public Builder e(String e) {
          map.put("e", e);
          return this;
        }
      
        public MyObject build() {
          return new MyObject(
            (Integer) map.get("a"), 
            (Boolean) map.get("b"), 
            (Integer) map.get("c"), 
            (Integer) map.get("d"), 
            (String) map.get("e"));
        }
      }
      

      【讨论】:

        猜你喜欢
        • 2020-06-26
        • 1970-01-01
        • 1970-01-01
        • 2015-10-11
        • 1970-01-01
        • 1970-01-01
        • 2021-11-11
        • 1970-01-01
        • 2011-07-28
        相关资源
        最近更新 更多