在构造函数中做了很多坏事?
使所有的领域final
是一个好主意,但有时我发现自己在构造函数中做所有事情。 最近我结束了一个实际上在构造函数中做所有事情的类,包括读取属性文件和访问数据库。
一方面,这是类是什么,它封装的数据读取,我喜欢创build对象完全初始化。 构造函数并不复杂,因为它代表了大部分的工作,所以看起来很好。
另一方面,这感觉有点奇怪。 而且,在17:58左右的讲话中 ,有很多理由不在施工方面做很多工作。 我想我可以通过传递适当的假设作为构造函数参数来消除这个问题。
问题依然存在:在构造函数中做了很多工作(甚至是所有的工作)都不好?
我认为“在构造函数中工作”是可以的
…只要你不违反单一职责原则(SRP) ,坚持使用dependency injection(DI) 。
我最近一直在问自己这个问题。 我发现构造函数中的工作动机是:
- 这使得很难testing
- 我见过的所有例子都是没有使用DI的地方。 这实际上不是构造函数做实际工作的错误。
- 您可能不需要您的构造函数计算的所有结果,浪费处理时间,并且很难单独进行testing。
- 这基本上是SRP的违规行为,而不是每个人都说做构造函数的错误。
- 旧的编译器在构造函数中抛出exception时遇到问题,因此除了在构造函数中赋值字段之外,不应该做任何事情。
- 考虑到历史编译器的缺陷,我认为编写新的代码不是一个好主意。 如果我们这样做的话,我们不妨去掉C ++ 11和所有这一切。
我的意见是…
…如果您的构造函数需要为遵守资源获取初始化(RAII)而做的工作,并且该类不违反SRP并且DI被适当地使用; 然后在构造函数中工作是A-好的! 你甚至可以抛出一个exception,如果你想阻止初始化失败的类对象的使用,而不是依靠用户来检查一些返回值。
这是一个非常开放的问题,所以我的答案会尽可能地尽可能地通用。
在构造函数中进行工作并不像过去几年那样“exception”,因为exception处理并不像现在这样普遍和发展。 您会注意到,Google技术讨论主要从testing的angular度来看待构造函数。 构造函数在历史上一直非常难以debugging,因此说话者是正确的,即在构造函数中尽可能less地做到更好。
有了这个说法,你会注意到他也触及了dependency injection/提供程序模式,这是复杂的构造函数臭名昭着的。 在这种情况下,只在提供者/ DI代码中留下构造函数是首选。 再次,答案取决于你正在使用什么模式,以及你的代码如何“适合”在一起。
使用构造函数的全部重点是创build一个可立即使用的整洁对象; 即new Student("David Titarenco", "Senior", 3.5)
。 没有必要做david.initialize()
因为这将是完全愚蠢的。
以下是我的一些生产代码,例如:
Config Conf = new Config(); Log.info("Loading server.conf"); Conf.doConfig();
在上面的例子中,我决定不对构造函数(它是空的)做任何事情,但有一个doConfig()
方法来完成所有的磁盘I / O; 我经常认为doConfig()
方法是毫无意义的,我应该在构造函数中做所有事情。 (毕竟,我只检查一次configuration文件。)
我认为这完全取决于你的代码,你不应该认为把“东西”放在你的构造函数中是一件坏事。 这就是构造函数的用途! 有时我们会被OOP( getThis
, setThat
, doBark
) setThat
,当一个类真正需要做的就是加载一个configuration文件。 在这种情况下,只需将所有内容放在构造函数中,并称之为一天!
通常情况下,如果你的对象有一个复杂的创buildalgorithm,你可以使用Builder或Factory来简化它。 特别是如果有前提条件被validation来build立对象。
一旦你开始使用build设者和工厂build立你的对象,他们可以validation前和后的条件,并确保您的代码的客户端将只能访问一个完全初始化的对象,而不是一个半制的 ,你甚至可以使用现今stream行的stream畅的界面来创build你的对象,并使其看起来很酷; D
new EmailMessage() .from("demo@guilhermechapiewski.com") .to("destination@address.com") .withSubject("Fluent Mail API") .withBody("Demo message") .send();
显然,这不是你的情况,因为这不是使用Builder,但它很像你可以构build的东西,使你的构造函数做更less的工作,让你的代码看起来更简单。
当我将过多的代码放入构造函数时,遇到以下问题:
- 很难为这个类的其他方法编写unit testing,因为它想在构造函数中做很多事情,所以我必须设置很多有效的东西,或者至less是模仿(DB,文件等等)为最简单的unit testing。
- 很难为构造函数本身编写unit testing。 无论如何,将多种多样的职责代码整合到一个区块中是一个坏主意。 (简单的责任原则。)
- 由于以前的原因,很难使用这个类。 例如,它完全阻止了我实现一些延迟的init方法,因为在调用构造函数的时刻它需要一切。 好的,我可以写懒惰的init方法到构造函数中,很好。
- 迟早我意识到重用一些放在构造函数中的代码部分是有意义的。 那么,当我第一次写构造函数的时候,我还认为那些代码部分将永远用在那里。
- 当我想扩展这个类并在超级构造函数的逻辑之前或之后插入一些逻辑时,它根本不起作用,因为在扩展类的构造函数中要做的第一件事是调用超类的构造函数。
所以是的,在我看来,在构造方面做很多事情是一个坏主意。
通常我只是将一些字段初始化放入构造函数中,并在每个人都在使用时调用init方法。
在我看来,有构造函数和析构函数是好的,但不要做太多的工作。 尤其是文件/数据库访问,除非它非常非常特定于类。 你想让你的构造函数/析构函数保持轻微,以保持程序的stream畅性。 有时候就像你已经来到了一个构造函数基本上完成所有工作的情况。 有办法让事情变得更轻。 这个概念/范例被称为懒惰评估。 这个想法是接受input,什么都不做(例如在构造函数中),但是在需要计算时需要使用input。
例如:假设你有一个读取文件的类,parsing它并告诉你文件中所有数字的总和。 你可以在构造函数中完成这一切。 使用懒惰评估你只会打开文件,并有一个getTotalSum()函数。 当被调用时,它将执行parsing并给出结果。 这样你也可以有getBestFit()来获得最适合的线条。 有时候,你不想要最适合自己的一些input。 这样用户就不会等待构造函数在用户决定要做什么之前进行计算。
另一个例子:可以说你有一个视图加载20个图像。 但是只显示了5个,构造函数需要显示一个图像的数组。 你可以把它们全部加载到构造函数中,但是从用户的angular度来看,这会在开始的时候感觉很慢。 或者你可以加载1“加载”的图片和一次加载1图像。 并且随着用户滚动加载更多的图像在所示/需要的基础上。
当然,1个问题是,你发现错误,如道路后面的无效图片,而不是构造函数。 您可以随时进行简单的检查,以在某种程度上预先validationinput(例如,检查密码是否正确)。