在构造函数中做很多是不好的吗?

使所有字段 final通常是一个好主意,但有时我发现自己在构造函数中做所有事情。最近,我发现一个类在构造函数中实际执行 一切,包括读取属性文件和访问数据库。

一方面,这就是类的用途,它封装了读取的数据,我喜欢创建完全初始化的对象。构造函数一点也不复杂,因为它委托了大部分工作,所以看起来很好。

另一方面,感觉有点奇怪。此外,在17:58左右的 这次谈话中,有很好的理由不在构造函数中做太多的工作。我想我可以通过传递合适的虚拟机作为构造函数参数来消除这个问题。

问题仍然是: 在构造函数中做大量的工作(甚至所有的工作)是不好的吗?

40859 次浏览

通常是这样的,如果您的对象有一个复杂的创建算法,那么您可以使用 Builder 或 Factory 来简化它。特别是在有要验证以构建对象的前提条件的情况下。

一旦你开始使用 Builders 和 Factory 来构建你的对象,他们可以验证前置和后置条件,并确保你代码的客户端只能访问一个完全初始化的对象,而不是 半成品对象,你甚至可以使用现在流行的接口来创建你的对象,使它看起来很酷;

new EmailMessage()
.from("demo@guilhermechapiewski.com")
.to("destination@address.com")
.withSubject("Fluent Mail API")
.withBody("Demo message")
.send();

显然,这不是你的情况,因为这不是使用一个生成器,但它更像是你可以构建的东西,使你的构造器做更少的工作,使你的代码看起来更简单。

这是一个非常开放式的问题,所以我的回答将尽可能笼统..。

在构造函数中执行工作并不像多年前那样“糟糕”,当时异常处理并不像今天这样普遍和发展。您会注意到,Google Tech 的演讲主要从测试的角度来看构造函数。构造函数在历史上一直都很难调试,所以说话者是正确的,在构造函数中尽可能少做是更好的。

说到这里,你会注意到他还提到了依赖注入/提供者模式,这种模式因复杂的构造函数而臭名昭著。在这种情况下,最好在构造函数中只保留提供程序/DI 代码。同样,答案取决于您正在使用什么样的模式以及您的代码如何“匹配”在一起。

使用构造函数 创建一个可以立即使用的简洁对象的全部要点,即 new Student("David Titarenco", "Senior", 3.5)。没有必要做 david.initialize(),因为这将是完全愚蠢的。

下面是我的一些生产代码,例如:

    Config Conf = new Config();
Log.info("Loading server.conf");
Conf.doConfig();

在上面的例子中,我决定不使用构造函数做任何事情(它是空的) ,而是使用一个 doConfig()方法来完成所有的磁盘 i/o; 我经常认为 doConfig()方法毫无意义,我应该在构造函数中完成所有的事情。(毕竟,我只检出配置文件一次。)

我认为这完全取决于您的代码,您不应该认为在构造函数中放入“ stuff”是一件坏事。这就是构造函数的作用!有时候我们被 OOP (getThissetThatdoBark)冲昏了头脑,而实际上一个类需要做的只是加载一个配置文件。在这种情况下,只需将所有内容放到构造函数中,然后就可以结束了!

在我看来,有构造函数和析构函数是好的,但是不要在它们中做太多的工作。特别是文件/数据库访问,除非它非常特定于类。您希望保持构造函数/析构函数轻巧,以使程序感觉流畅。有时,比如已经遇到了构造函数实际上完成所有工作的情况。有一种方法可以让事情变得更轻松。这个概念/范例称为惰性评估。其思想是接受输入,什么也不做(例如在构造函数中) ,但是在需要计算时使用输入。

例如: 假设你有一个类,它读取一个文件,解析它并告诉你文件中所有数字的总和等信息。您可以在构造函数中完成这一切。使用延迟计算只会打开文件,并且有一个 getTotalSum ()函数。当调用它时,它将执行解析并给出结果。通过这种方式,您还可以使用 getBestFit ()来获得最佳线条。有时你不想得到最好的适合和一些投入你做的。这样,在用户决定做什么之前,用户就不必等待构造函数执行计算。

另一个例子: 假设您有一个视图,加载20个图像。但是只显示了5个,并且构造函数接受一个要显示的图像数组。您可以在构造函数中全部加载它们,但是从用户的角度来看,这在开始时会感觉很慢。或者你可以加载一个“加载”图片和加载一个图像在一个时间。当用户滚动时,按照所示/所需的基础加载更多的图像。

当然,第一个问题是,你发现的错误,如无效的图片后来的道路上,而不是构造函数。你可以经常为自己进行简单的检查,在某种程度上预先验证输入(例如,检查正确的密码)。

当我在构造函数中放入太多代码时,我遇到了以下问题:

  • 很难为该类的其他方法编写单元测试, 因为它想在构造函数中做很多事情,所以,我 必须设置许多有效的东西或至少模拟(数据库,文件, 对于最简单的单元测试。
  • 很难为构造函数本身编写单元测试, 将大量具有多样化职责的代码放入一个 阻塞甚至是一个坏主意
  • 由于前面的原因,很难使用这个类, 它完全阻止了我实现一些延迟的 init 方法, 因为在调用 好的,我可以将惰性 init 方法写入 构造函数,很好。
  • 迟早我会意识到重用一些代码部分是有意义的 它们被放置在构造函数中。当我第一次写 构造函数,我还认为这些代码部分将只用于 那里,永远。
  • 当我想要扩展那个类并在或之前插入一些逻辑时 进入超级构造函数的逻辑,它根本不工作,因为 在扩展类的构造函数中要做的第一件事是调用 管理员是一号。

所以,是的,在构造函数中做很多工作在我看来是一个坏主意。

通常,我只是在构造函数中放入一些字段初始化,然后创建一个 init 方法,以便在所有人都参与时调用。

我认为“在构造函数中做功”是可以的..。

... 只要你不违反 单一责任原则和坚持使用 依赖注入

我最近也一直在问自己这个问题。反对在构造函数中工作的动机是:

  • 所以很难测试
    • 我看到的所有例子都是没有使用 督察的。实际上这不是构造函数做实际工作的错误。
  • 您可能不需要构造函数计算的所有结果,这浪费了处理时间,而且很难单独测试。
    • 这基本上违反了 SRP,而不是构造函数每个 say 做功的错误。
  • 旧的编译器在构造函数中抛出异常时会遇到麻烦,因此除了在构造函数中分配字段之外,您不应该做其他任何事情。
    • 我不认为在编写新代码时考虑到历史编译器的缺陷是一个好主意。我们不妨去掉 C + + 11,如果我们这样做的话,所有这些都是好的。

我的意见是..。

... 如果你的构造函数需要做功才能符合 RAII,而且这个类没有违反 SRP,并且 督察被正确使用; 那么在构造函数中做功就是 A-OK!如果您希望防止使用初始化完全失败的类对象,而不是依赖用户检查某个返回值,那么甚至可以引发异常。