Java 如何设计构造函数?

Java 如何设计构造函数?,java,constructor,Java,Constructor,让我们假设一个类Foo有两个实例变量,intx和inty。用例要求我可以使用无参数、单个参数或所有参数构造类 input X input Y 0 0 no constuctor 0 1 public Foo(int x); 1 0 public Foo(int y); 1 1 public Foo(int x, int y)

让我们假设一个类Foo有两个实例变量,intx和inty。用例要求我可以使用无参数、单个参数或所有参数构造类

input X    input Y

  0         0           no constuctor

  0          1          public Foo(int x);

  1          0          public Foo(int y);

  1          1          public Foo(int x, int y);

在这种情况下,惯例/最佳做法是什么。我需要添加所有构造函数的排列吗?如果是,它将导致指数增长。如果没有,那么解决方案是什么?

简短回答:这取决于具体情况

更详细的回答:添加与实际用例相对应的用例。如果这意味着一个包含所有参数,或者一个没有设置默认参数的参数,那就太好了。如果有一些参数为默认值,而有些参数为非默认值的常见用例,请为此提供构造函数


请注意,不能有多个具有相同顺序、数量和参数类型的构造函数。

最好使用中的生成器模式

new FooBuilder().build()   // Uses default values for both parameters

new FooBuilder().setX(42).build()  // Uses default value for y

new FooBuilder().setY(-13).build()  // Uses default value for x

new FooBuilder().setX(42).setY(-13).build()  // Supplied values for both
然后你的建筑工人可能看起来像

class FooBuilder {
  public Foo build() { return new Foo(...); }
  public FooBuilder setX(int x) { ...; return this; }
  public FooBuilder setY(int y) { ...; return this; }
}

对于这种情况,一种常见的方法是使用builder模式

构建器的思想是,您有一个中间对象,它可以理解各种必需或可选参数,并用于生成适当构造的对象。这意味着您只需要在实际业务对象中使用all args构造函数,这可以大大简化工作

您可以通过执行以下操作来使用生成器:

Foo foo = new FooBuilder().withX(10).withY(25).build();

Project Lombok提供了一个非常有用的@Builder注释,它将为您解决所有这些问题:有关详细信息,请参阅。

拥有一个接受对象列表/数组的构造函数。在构造函数中迭代它,并根据需要设置值。我假设您的类中所有可用字段都有setter

优点:您不需要编写不同的构造函数

缺点:您需要知道并理解,您在列表/数组中的索引或位置决定了要设置的字段

Foo (Object[] args){
this.setX(args[0]); // have null check accordingly
this.setY(args[1]);    
}

正如@Platinum Azure所说,这要看情况而定。既然你没有说过有一个占主导地位的用例

请记住,作为类设计器,我们是为客户机程序员设计的。他们的代码对第三人来说有多清晰?他们怎么知道这个词的意思

Foo foo = new Foo();
Foo foo2 = new Foo(2);
Foo foo23 = new Foo(2,3);
你希望他们遵循什么样的代码风格?EverythingNoneLine样式:

Foo foo = new Foo().setNumHeads(2).setNumLegs(3);
当有许多事情需要设置时,可以对其进行调整

Foo foo = new Foo()
    .setNumHeads(2)
    .setNumLegs(3);
或者传统的OO风格:

Foo foo = new Foo();
foo.setNumHeads(2)
foo.setNumLegs(3);
那么,你现在需要做多少工作?您是否需要一个构建器,或者像上面的例子那样,Foo是否足够简单而不需要

另一种方法是提供默认值:

Foo foo = new Foo(Foo.DefaultNumHeads, Foo.DefaultNumLegs, Foo.DefaultNumTails);
它可以很好地处理一些参数,但是如果将默认值定义为int,那么在有很多参数的情况下很容易出错。因此,您可以应用命名参数习惯用法的变体。这是一些人建议建筑商的地方,如,或

其中Foo.DefaultNumHeads等被定义为最终对象。到你到达的时候

Foo foo = new Foo(Foo.NumHeads(2), Foo.NumLegs(3), Foo.NumTails(5));
您正在Foo内部编写一个生成器

在Foo生活的生态系统中,什么样的构造函数是常见的?如果其他类已经有类似的参数,那么Foo也应该有类似的参数

关于组合爆炸的观点取决于预期会发生什么变化。如果通过抽象,Foo不是一个很好的想法,我们会得到一个组合爆炸,并且我们会返回并一次又一次地改变Foo,违反了。不要这样做


因此,这要视情况而定。

在同一个类中不能将Fooint x和Fooint y作为单独的构造函数,因为它们具有相同的签名。在建议的解决方案中,您也需要有setX、setY等方法……@Rupesh,是的。建设者有二传手。构建类没有。这个解决方案的好处是,您可以使用可变的构建器来创建不可变的对象。您还可以取消方法名称的设置部分,以提供一种有时更可读的方法:new FooBuilder.x42.y-13.build;显然取决于一个人的风格/偏好。最后,我最喜欢的构建器模式的好处是,您可以在构建对象中创建更简单的构造函数,因为构建器的存在是因为构建构建对象的复杂性,这应该提供更干净、更可测试的API,假设人们当然不介意构建器。从现在起,我将在每个答案中使用它,当然还有引用!这并不总是最好的答案,但这个问题有可能过于集中而忽略了要点-缺点还应包括不必要的装箱/拆箱,以及静态类型检查的丢失。
Foo foo = new Foo(Foo.NumHeads(2), Foo.NumLegs(3), Foo.NumTails(5));