构造函数参数 - 经验法则

23

一般来说,一个类的构造函数最多应该接受多少参数?我正在开发一个需要大量初始化数据的类(目前有10个参数)。然而,一个有10个参数的构造函数感觉不太对。这让我相信我应该为每个数据创建一个getter/setter。不幸的是,getter/setter模式并不能强制用户输入数据,没有它对象的特征描述是不完整的,因此无用的。您怎么看?

9个回答

38

有这么多参数,现在是考虑Builder模式的时候了。创建一个Builder类,其中包含所有那些getter和setter方法,以及一个build()方法,该方法返回您真正想要构建的类的对象。

示例:

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在2007年JavaOne上的演讲。(这也是Effective Java 2nd Edition中的第2项,但我手头没有它,所以无法引用。)

2
如果您使用构建器模式,这也是在结果对象上使用final字段的机会。 - Jason S
1
/* 在这里填写必需的参数 */如果我理解问题正确,在这种情况下,所有十个参数都是必需的。因此,建造者模式在这里并没有真正帮助,因为仍然有一个具有十个参数的构造函数。 - Kayz
如果您的语言支持,可以查看https://en.wikipedia.org/wiki/Named_parameter。不幸的是,Java仍然不支持。 - YAMM

13

你可以自行决定何时足够,并使用引入参数对象来构建你的构造函数(或者任何其他方法)。


但是请确保你不只是把复杂性从一个地方移到另一个地方:持有10个参数的Parameter对象的构造函数会是什么样子?你需要为你的Parameter对象创建一个Parameter对象吗?;-) - bendin
2
@bendin:我只是说出来而已,无论是好的还是坏的代码都可以用它编写。:) - Jason Punyon

9

《代码大全2》建议每个方法的参数不要超过七个,这是比较明智的限制。

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


6

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

对于构造函数而言,一个很好的经验法则是传递一个对象的标识,而不是它的状态。你传递的这些参数是对象存在所必需的,并且如果没有它们,大多数对象操作可能无法进行。

如果你确实有一个具有非常复杂的自然标识的类,因此需要许多参数,请考虑你的类的设计。

一个糟糕的构造函数的例子是:

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

这里NightWatchman正在使用一些默认值进行构建,这些默认值很可能在短时间内发生变化。有趣的是,对象以一种方式告知其值,然后通过设置器以不同的方式拥有它们。

一个更好的构造函数示例:

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

看门人看守的大门是存在的必要信息。在类中,我会将其标记为private final。 我选择将shootOnSight变量传递到构造函数中,因为这里很重要的一点是对象始终知道是否应该射击入侵者。这里使用身份作为类型。 我可以有一个名为ShootingGateWatchmanPoliceCallingGateWatchman的类-即参数被解释为对象身份的一部分。

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

2
通常来说,我会说不超过五个,基于短期记忆的7±2法则以及对程序员注意力持久性的一些悲观看法。注意:我会将可变参数列表视为一个实体。
如果你真的被限制在一次构建对象中,你通常可以将相关的参数收集到简单值对象中,并将它们传递给构造函数。尽量确保这些值对象有一定的概念意义,而不仅仅是随机信息的集合...

“短期记忆规则”的加分项 - Olle89

2
我需要了解这个类是做什么的以及参数是什么,但有可能这个类拥有太多职责。是否有可能将类分成更小的独立类?
使用setter并不能解决类具有许多依赖/参数的问题。它只是把问题移到另一个地方,而且不强制输入参数。
对于方法,我尝试遵循《Clean Code》书籍中的建议,每个方法不超过3个参数(如果我没记错的话)。对于构造函数,我可能会有更多的参数,因为通常构造函数会被我的依赖注入框架调用,而不是由我自己调用。
Mmyers提到的生成器模式也是构建复杂对象的好方法,没有办法使它们变得更简单。

我点赞是因为我是提出责任问题的人...相当确定这个类做了太多的事情。 - vmrvictor

0

这要看情况。

如果一些参数是相同类型的,可以混合使用,我可以容忍较小的数量(比如5个)。

如果参数是不同类型的,不能混合使用,那么我可以容忍更多。但是10个就接近极限了。


-1

构造函数传入一个参数 => 值映射对象怎么样?如果调用者省略了任何关键参数,构造函数将抛出异常。

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接