具有大参数或Java bean getter/setter方法的Java构造函数

dee*_*o55 26 java constructor

我无法确定哪种方法更适合创建具有大量字段(10+)(所有必需的)getter/setter的构造函数方法的对象.构造函数至少要强制设置所有字段.Java Bean更容易看到正在设置哪些变量而不是庞大的列表.构建器模式似乎并不合适,因为所有字段都是必需的,构建器要求您将所有必需参数放在构建器构造函数中.

感谢:D

cle*_*tus 31

更好的方法(imho)是使用某种构建器:

MyClass a = new MyClassBuilder().blah("blah").foo("foo").doStuff().toMyClass();
Run Code Online (Sandbox Code Playgroud)

其中MyClass仍然是不可变的,但具有比具有10个参数的构造函数更可读的创建.

这也称为流畅的界面.Josh Bloch在Effective Java中提到了这一点.

  • 鉴于所有十个领域都是强制性的,我强烈反对.如果每个字段都是可选的并且充当修饰符(即:Hibernate的SQLQuery),我同意,使用流畅的界面将是最好的,但是当需要所有十个时,代码将是巨大的,并且它将允许创建一个不完整的对象. (19认同)
  • 我认为这些评论误解了Builder模式.如果您需要不变性,请参阅我的答案中的链接,将其作为嵌套类,但Builder可以与对象位于同一个包中,更重要的是可以在调用构建方法时验证所有参数都已设置.构建器对象是与类不同的对象,因此流畅的接口不会在任何阶段创建不完整的对象. (9认同)
  • 因为构造变成了内部实现问题而不是外部接口问题.你更愿意拥有什么:100个带有可读构建器的代码和1个凌乱的内部构造函数或100个凌乱的10个arg构造函数? (5认同)
  • @ deelo55如果你的构造函数有10个args,那么很难记住每个arg所处的位置(例如,字体名称是第四个arg还是第五个?).使用构建器,您可以合理地替换命名参数,因此您不必记住每个arg的位置. (4认同)
  • 将10个字符串放在一起,即使它们都是强制性的,也可能是错误加上它很难读.这可以根据需要进行调整.例如,调用具有2-4个逻辑分组参数的函数.大多数情况下,具有超过约4个参数的方法是不合需要的.不幸的是,Java(与Javascript不同)没有一种方便的方法来创建匿名对象来处理这种情况. (3认同)

Bri*_*new 27

我的第一个想法是检查你的封装模型是否正确.拥有10个以上的必填字段听起来相当多,也许在这种情况下拥有更细粒度的组件会更有意义吗?

这些字段/参数中的一些是否相关?它们可以组合成有意义的对象(例如x-coordinate,y-coordinate组合成Point对象等)


Car*_*icz 20

史蒂夫麦康奈尔在其着作"完整的代码"中辩称,任何程序都不应超过最多6个,也许7个参数.这些陈述中的大多数不仅仅是他的观点,而是以研究为后盾,例如与代码结构相关的错误率.

罗伯特·马丁的清洁代码甚至更进一步:他建议使用1或2个参数,而3则已经被认为是"代码味道".就个人而言,我认为清洁代码在某些地方有点极端,但总的来说它提出了一些好的论据.

"一大堆参数"(无论多少参数)都表明"厨房水槽"设计有很多事后的想法和很少的结构.它还使维护更加困难和容易出错; 至少,它使代码难以阅读.

所有这些都有充分的理由考虑减少参数的数量.其他答案提供了一些实用的建议.

  • 这是真的:)当有人向我提到10-arg构造函数时,我会产生怀疑.但麻烦不是构造函数,而是10个不同对象形成有效类的必要性.它表明概念正在以某种方式混合在一起. (2认同)
  • @extraneon:虽然我主张干净利落地做事,但我也可以看到另一面:几年前,一位同事设计了一个具有大量属性的班级; 这些属性实际上都是对象的一部分,需要为所有属性指定值.没有属性,该对象就没有意义.他最终构建了一个包含大约30个参数的1500行构造函数.我感到很震惊,但当时我无法提供令人信服的论据,否则这些代码已经投入生产多年了!:) (2认同)

Yis*_*hai 16

我建议你在这种情况下考虑构建器模式.您可以保证获得有效的对象,而无需拥有大量参数.

OP是更新以拒绝构建器模式,但它似乎是基于误解.Builder模式存在的事实不会删除所有参数的强制执行.

考虑以下对象:

 public class SomeImmutableObject {
      private String requiredParam1;
      private String requiredParam2;
      //etc.

      private SomeImmutableObject() { //cannot be instantiated outside the class }

      public static class Builder {
          private SomeImmutableObject instance;
          public Builder() { instance = new SomeImmutableObject();
          public Builder setParameter1(String value) {
               instance.requiredParam1 = value;
               return this;
          }
          //etc for each parameter.

          public SomeImmutableObject build() {
             if (instance.requiredParam1 == null || instance.requiredParam2 == null /*etc*/)
                throw new IllegalStateException("All required parameters were not supplied.");
             return instance;
          }
      } 
 }
Run Code Online (Sandbox Code Playgroud)

请注意,通过将字段包设为私有并将构建器放在同一个包中,您可以完成基本相同的操作.

如果由于某种原因你不能这样做,你仍然可以使用10个参数的构造函数,然后让Builder成为调用该构造函数的唯一东西,这样它就是一个更容易使用的API.

因此,对于所有声明的要求,Builder模式工作得很好.需要所有10个参数这一事实并不会取消构建器模式的资格.如果模式不满足其他需要,请详细说明.

编辑:OP添加了一个评论(很久以前,但我刚刚在这个问题上得到了一个upvote,所以我现在才看到它),并提出了一个有趣的问题:如何在以后的某个时间点验证原语?

有几种方法可以解决这个问题,包括一个保护布尔值,但我首选的方法是使用Double对象:

     private Double doubleForPrimitive;

     public Builder setDouble(double d) {
         doubleForPrimitive = d;
     }

     public SomeImmutableObject build() {
         if(doubleForPrimitive != null) {
               instance.doubleParam = doubleForPrimitive;
         } else {
                throw new IllegalArgumentExcepion("The parameter double was not provided");
         }
         //etc.
     }
Run Code Online (Sandbox Code Playgroud)

应该注意的是,如果你需要真正的线程安全不变性,将不可变对象的所有字段都作为final,这需要更多样板(将变量存储在构建器中并将它们传递给不可变对象的私有构造函数),但是从客户端代码的角度来看,这种方法仍然很简洁.


JRL*_*JRL 2

恕我直言,您应该根据构造函数中的业务逻辑传递对象有效所需的所有内容。

如果参数列表很长,您可以创建一个包含参数的对象并传递它。

  • 您将如何构造包含参数的对象?一路下来都是乌龟! (2认同)