构造函数参数 - 经验法则

时间:2009-04-08 14:11:53

标签: java design-patterns refactoring

一般来说,类构造函数应该接受的参数的最大数量是多少?我正在开发一个需要大量初始化数据的类(目前有10个参数)。但是,具有10个参数的构造函数感觉不对。这让我相信我应该为每个数据创建一个getter / setter。不幸的是,getter / setter模式不会强制用户输入数据,如果没有它,对象的表征就不完整,因此无用。想法?

9 个答案:

答案 0 :(得分:35)

有了这么多参数,就该考虑Builder pattern了。使用build()方法创建一个包含所有这些getter和setter的构建器类,该方法返回您正在尝试构建的类的对象。

实施例

public class ReallyComplicatedClass {
    private int int1;
    private int int2;
    private String str1;
    private String str2;
    // ... and so on
    // Note that the constructor is private
    private ReallyComplicatedClass(Builder builder) {
        // set all those variables from the builder
    }
    public static class Builder {
        private int int1;
        private int int2;
        private String str1;
        private String str2;
        // and so on 
        public Builder(/* required parameters here */) {
            // set required parameters
        }
        public Builder int1(int newInt) {
            int1 = newInt;
            return this;
        }
        // ... setters for all optional parameters, all returning 'this'
        public ReallyComplicatedClass build() {
            return new ReallyComplicatedClass(this);
        }
    }
}

在您的客户端代码中:

ReallyComplicatedClass c = new ReallyComplicatedClass.Builder()
        .int1(myInt1)
        .str2(myStr2)
        .build();

参见Effective Java Reloaded [pdf]的第7-9页,Josh Bloch在JavaOne 2007上的演讲。(这也是Effective Java 2nd Edition中的第2项,但我没有方便,所以我可以'引用它。)

答案 1 :(得分:12)

您可以决定何时足够,并使用Introduce Parameter Object作为构造函数(或任何其他方法)。

答案 2 :(得分:8)

Code Complete 2建议对任何方法都有相当合理的七个参数限制。

尝试为某些成员建立合理的默认值。这将允许您使用getter / setter而不必担心表征不完整。

答案 3 :(得分:4)

我认为你不能说合适的数字是“七,不多”或“五”。

构造函数的一个好的经验法则是将对象的标识,而不是状态传递给对象。您传入的参数是对象存在所必需的参数,如果没有这些参数,对象的大多数操作都可能无法实现。

如果你真的有一个具有非常复杂的自然身份的课程,因此需要很多参数,那么请考虑你班级的设计。

错误构造函数的一个示例是:

public NightWatchman(int currentFloor, int salary, int hapiness) {...}

这里的NightWatchman正在构建一些默认值,几乎肯定会在短时间内发生变化。有趣的是,对象以一种方式被告知其价值,然后将来以不同的方式(通过他们的制定者)将它们存在。

更好的构造函数的一个例子是:

public GateWatchman(Gate watchedGate, boolean shootOnSight) {...}

守望者正在观看的大门是一个存在的必需信息。在课堂上,我会将其标记为私人决赛。 我已经选择将 shootOnSight 变量传递给构造函数,因为重要的是,对象始终知道是否要射杀窃贼。在这里,身份被用作类型。

我可以有一个名为 ShootingGateWatchman PoliceCallingGateWatchman 的类 - 即该参数被解释为对象标识的一部分。

答案 4 :(得分:2)

我建议找到参数之间的依赖关系,然后创建结构或其他类来保存它们并将它们传递给你的构造函数,而不是乍一看似乎没有相关的一堆东西。

答案 5 :(得分:2)

我需要更多地了解该类的作用以及参数是什么,但是该类可能有太多的责任。是否可以将类拆分为较小的独立类?

使用setter不能解决具有许多依赖项/参数的类的问题。它只是将问题移到另一个地方,并且不会强制输入参数。

对于方法,我尝试遵循清洁代码书中的建议,每个方法(IIRC)的参数不超过3个。对于构造函数,我可能有更多参数,因为通常构造函数将由我的依赖注入框架调用,而不是由我调用。

mmyers提到的构建器模式在构建复杂对象时也是一个很好的解决方案,并且没有办法使它们变得不那么复杂。

答案 6 :(得分:1)

我通常会说7+/-2 rule of short term memory不超过五,而且对程序员的注意力有一些悲观情绪。注意:我将varargs列表视为一个实体。

如果你真的被限制一次构造对象,你通常可以在简单的值对象中收集相关参数并将它们传递给构造函数。尽量确保价值对象具有一定的概念意义,而不仅仅是随机的信息集合......

答案 7 :(得分:0)

取决于。

如果某些参数类型相同且可以混淆,我会容忍一个相当小的数字(例如5)。

如果参数属于不同的类型,所以它们不能混淆,那么我会容忍更多。尽管如此,十分接近极限。

答案 8 :(得分:-2)

如何传入Param => Value Map对象到构造函数?如果调用者省略了任何关键参数,请让构造函数抛出异常。

这意味着对构造函数的错误调用只会在运行时而不是编译时捕获,这是一个缺点。但是getter / setter方法有同样的问题,这应该更容易使用。