在构造函数中做了很多坏事吗?

时间:2011-08-13 04:11:24

标签: java constructor

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

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

另一方面,感觉有点奇怪。此外,在大约17:58的this talk中,有充分的理由不在构造函数中做很多工作。我想我可以通过传递适当的假人作为构造函数参数来消除这个问题。

问题仍然存在:构建器中的大量工作(甚至是所有工作)都不好吗?

5 个答案:

答案 0 :(得分:34)

我认为“在构造函数中完成工作”是可以的......

...只要您不违反Single Responsibility Principle (SRP)并坚持使用Dependency Injection (DI)

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

  • 这使得测试变得困难
    • 我见过的所有例子都是未使用DI的地方。这实际上并不是构造函数执行实际工作的错误。
  • 您可能不需要构造函数计算的所有结果,浪费处理时间并且很难单独测试。
    • 这基本上违反了SRP,而不是构造函数按照说法工作的错误。
  • 旧编译器在构造函数中抛出异常时遇到问题,因此除了在构造函数中赋值字段外,您不应该执行任何操作。
    • 我认为编写考虑到历史编译器缺陷的新代码并不是一个好主意。如果我们这样做的话,我们可能会废除C ++ 11以及所有这些都很好。

我的意见是......

...如果您的构造函数需要为其工作以遵守Resource Acquisition Is Initialization (RAII)并且该类未违反SRPDI已正确使用;然后在构造函数中工作就是A-Okay!如果您想要防止使用初始化失败的类对象而不依赖于用户检查某些返回值,您甚至可以抛出异常。

答案 1 :(得分:16)

这是一个非常开放的问题,所以我的答案将尽量尽可能一般......

在构造函数中工作并不像以前那样“糟糕”,因为异常处理并不像现在这样流行和演变。您会注意到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()方法毫无意义,我应该在构造函数中做所有事情。 (毕竟,我只查看配置文件一次。)

我认为它完全依赖于你的代码而你不应该认为在构造函数中添加“东西”是件坏事。这就是构造函数的用途!有时,当真正所有类需要做的是加载配置文件时,我们会被OOP(getThissetThatdoBark)带走。在这种情况下,只需将所有内容放在构造函数中并将其称为一天!

答案 2 :(得分:12)

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

一旦您开始使用Builders和Factories构建对象,他们可以验证前置和后置条件,并确保代码的客户端只能访问完全初始化的对象而不是半成品一,您甚至可以使用时尚的流畅界面来创建您的对象并使其看起来很酷; D

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

显然这不是你的情况,因为这不是使用Builder,但它很像你可以构建的东西,以使你的构造函数减少工作量并使代码看起来更简单。

答案 3 :(得分:11)

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

  • 很难为该类的其他方法编写单元测试, 因为它想在构造函数中做很多事情,因此,我 必须设置很多有效的东西或至少嘲笑(DB,文件, 无论如何最简单的单元测试。
  • 很难为构造函数本身编写单元测试。无论如何, 将大量具有多样化责任的代码整合到一起 块甚至是个坏主意。 (Single Responsibility Principle。)
  • 由于前一个原因,很难使用该课程。例如, 它完全阻止我实现一些延迟的init方法, 因为它在调用时需要一切 构造函数。好吧,我可以把懒惰的init方法编写进去 构造函数,很好。
  • 迟早我意识到重用一些代码部分是有意义的 它们放在构造函数中。好吧,当我第一次写的时候 构造函数我也认为那些代码部分将被使用 那里,永远。
  • 当我想扩展该类并在或之前插入一些逻辑时 进入超级构造函数的逻辑,它根本没有用,因为 在扩展类的构造函数中要做的第一件事就是调用 超级的。

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

通常我只是将一些字段初始化放入构造函数中,并在每个人都加入时调用init方法。

答案 4 :(得分:2)

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

示例:假设您有一个类读取文件,解析它并告诉您信息,如文件中所有数字的总和。您可以在构造函数中完成所有操作。使用延迟评估,您只需打开文件,并具有getTotalSum()函数。调用它时会进行解析并给出结果。这样你也可以使用getBestFit()来获得最佳拟合线。有时候你不想得到最合适的东西以及你做的一些投入。这样,在用户决定做什么之前,用户不会等待构造函数进行计算。

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

当然,有一个问题是,您可以在以后的路上找到无效图片等错误而不是构造函数。您可以随时对自己执行简单检查,以在一定程度上预先验证输入(例如,检查密码是否正确)。