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

时间:2009-10-27 16:30:57

标签: java constructor

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

谢谢, d

18 个答案:

答案 0 :(得分:31)

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

MyClass a = new MyClassBuilder().blah("blah").foo("foo").doStuff().toMyClass();

其中MyClass仍然是immutable但是具有比具有10个参数的构造函数更可读的创建。

这也称为fluent interface。 Josh Bloch在Effective Java中提到了这一点。

答案 1 :(得分:27)

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

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

答案 2 :(得分:20)

答案 3 :(得分:16)

我建议你在这种情况下考虑builder pattern。您可以保证获得有效的对象,而不仅仅是拥有大量参数。

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

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

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

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

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

有一些解决这个问题的方法,包括一个保护布尔值,但我首选的方法是使用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.
     }

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

答案 4 :(得分:4)

这两种模式对于考虑这种情况非常有用:

答案 5 :(得分:4)

您可以考虑使用builder pattern,构建器确保所有字段至少设置为合理的默认值。请参阅实施链接,但最终会看到类似于以下内容的电话:

Widget widge = new Widget.Builder(). manufacturer("333").serialNumber("54321").build();

答案 6 :(得分:3)

构造函数的十个参数很多。我会认真思考它们是否都是必需的,而且其中一些组合成逻辑对象是没有意义的。如果真的有十个不同的必需数据,那么构造函数应该包含十个字段。

答案 7 :(得分:2)

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

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

答案 8 :(得分:2)

我会像这样实现构建器模式:

package so1632058;

public class MyClass {
  private final String param1;
  private final String param2;

  MyClass(Builder builder) {
    this.param1 = builder.param1;
    this.param2 = builder.param2;
  }

  public String getParam1() {
    return param1;
  }

  public String getParam2() {
    return param2;
  }

  @SuppressWarnings("hiding")
  public static final class Builder {
    String param1;
    String param2;

    public Builder param1(String param1) {
      this.param1 = param1;
      return this;
    }

    public Builder param2(String param2) {
      this.param2 = param2;
      return this;
    }

    public MyClass toMyClass() {
      return new MyClass(this);
    }
  }
}

然后使用以下代码:

package so1632058;

public class Main {

  public static void main(String[] args) {
    MyClass.Builder builder = new MyClass.Builder();
    builder.param1("p1").param2("p2");
    MyClass instance = builder.toMyClass();
    instance.toString();
  }

}

一些注意事项:

  • 没有多种参数的方法。
  • 可以在MyClass构造函数中完成附加检查。
  • 我在整个软件包范围内制定了构造函数,以避免出现“合成访问”警告。
  • 与构建器的实例字段相同。
  • 构建MyClass实例的唯一方法是通过Builder

答案 9 :(得分:1)

真的取决于具体的课程。它应该是不可变的吗?它是一个没有任何行为的简单值对象吗?您是要将此值对象映射到Web服务参数还是关系数据库?你要序列化吗? (其中一些东西需要一个默认的构造函数)。你能谈谈这个对象吗?

答案 10 :(得分:1)

该类的变体是否可能需要较少的参数,或者只有一个并且它有十个属性?

对象是否意味着不可变?

就个人而言,我没有看到大型构造函数有任何问题,特别是如果只有一个构造函数,并且所有属性都是最终的。

答案 11 :(得分:1)

如果所有参数实际上都是强制性的,那么我认为没有理由不使用构造函数。但是,如果情况并非如此,那么使用构建器似乎是最好的方法 在我看来,仅依赖于setter是最糟糕的解决方案,因为没有任何东西可以强制执行所有强制属性。当然,如果您使用的是Spring Framework的bean连接或其他类似的解决方案,那么Java bean就可以完全正常,因为您可以在初始化后检查所有内容已经设置完毕。

答案 12 :(得分:1)

毫无疑问,这是一个设计问题。你必须轻松权衡可读性。十个arg构造函数更容易,但可能或可能不是更易读/可维护。它还具有较少的方法调用来调用和调用调用堆栈。通过setter设置十个不同的值更具可读性和显性。它不一定“更容易”(虽然您可以双向争论),并添加更多方法调用来打开和关闭调用堆栈。

但是还有一些要考虑的问题。即使使用十个参数构造函数,您也可以让程序员选择传入null,空白,false或零(取决于对象或原语),这可能是您想要的也可能不是。控制它的唯一方法是在构造函数中抛出异常。这是你真正想做的吗?这是你期望的行为吗?

当然,通过setter分别设置每个变量,您可能无法知道何时构建对象或未构建对象。这是上面讨论的Builder模式有用的地方。让它创建对象,设置值,然后验证所有设置。如果缺少某些东西,因为程序员决定不传递内容,那么你就会受到保护。你的班级不必做比预期更多的事情。 (毕竟,考虑一下有朝一日可能会使用你的课程是很重要的。尽管世界上有很多伟大的Javadoc,但他们可能无法理解你的意图。)

最后,我会问你是否需要违约?因为如果某些东西可以默认,那么你可以在类级别将它们设置为默认值,或者在构造函数中将它们设置为默认值(取决于你的编程理想,你觉得它更具体,并协助你对象的行为) 。然后,您可以“预设”某些字段,只需要通过手动设置器或通过构建器通过构建器覆盖它们。

同样,你必须自己决定这些事情。但可能最重要的是考虑可读性而不是效率,以使代码更易于维护,并创建API和行为,让您之后的程序员无法滥用。无论您使用什么,都要在设计中预见滥用保护。

答案 13 :(得分:1)

答案 14 :(得分:1)

这在摘要中很难回答。 真正需要做的是查看这十个参数并查看它们是什么。我认为这些是关键问题:

  • 可以将其中一些组合成更高级别的“价值”对象吗?例如,变量X和Y可以组合成Point。我们在货物路线选择程序中有很多这样的情况,其中所有字段都被建模为原始字符串。引入一些更高级别的概念确实有助于使其可读。
  • 某些参数可以“默认”为某些值吗?
  • 他们真的都是独立的,正交的概念吗?我曾经在许多系统上工作,从未见过这是真的。如果没有,可以考虑一下这些。

答案 15 :(得分:0)

我会避免使用大量参数的构造函数。构造函数中包含大量参数的类可能很难处理。想象一下,如果你有一个继承heirarchy与子类,每个子类在其构造函数中有许多参数。如果需要更改某些顶级类的参数,将会有多少工作量。

我会将一个接口传递给你的构造函数,你可以在不破坏代码的情况下进行更改,或使用Javabeans方法并且没有arg构造函数。

答案 16 :(得分:0)

您的字段可以合并为中间对象吗?例如,如果要传入10个描述人物的字段,则创建一个PersonInfo对象以传递该数据。我个人更喜欢在实例化对象时传入所有必填字段。这样你就不会得到一个不可避免地被使用和滥用的半生不熟的物体。

答案 17 :(得分:0)