我一直认为,一般来说,类的主要工作应该在其实例方法中完成,而构造函数应该只将实例置于可用的初始状态。
但我发现在实践中有些情况下,将基本上所有实际工作放入构造函数似乎更有意义。
一个例子:我需要从数据库中检索一些特定于DBMS的信息。对我来说最自然的方式似乎是有一个类DBMSSpecInfo,带有一个构造函数:
public DBMSSpecInfo(java.sql.Connection conn) throws SQLException{
// ... retrieve info from DBMS
}
/** @returns max size of table in kiB */
public int getMaxTableSize() {//...}
/** @returns max size of index in kiB */
public int getMaxIndexSize() {//...}
/** @returns name of default schema */
public String getDefaultSchema() {//...}
您将构造一次类,构造函数将获取所有数据,然后您可以使用各种getter来检索所需的信息。
当然我可以将方法放在其他地方,并且只使用DBMSSpecInfo
作为返回值(实际上只使用DBMSSpecInfo作为值持有者),但是创建一个类只是为了从一个返回的值中感觉很难看单一功能。
那你觉得怎么样?在构造函数中执行主要工作是否有问题?它在Java中是“非惯用的”吗?或者这是一种可接受的(虽然可能不常见)的做法?
答案 0 :(得分:10)
主要的实际问题是单元测试 - 如果不进行实际工作,您将无法实例化对象。 (或者你必须模拟参与这项工作的所有课程。)
相关说明:OO Design for testability。它给出了为什么在构造函数中工作不利于单元测试的例子。
答案 1 :(得分:6)
在这种情况下,我更希望将创建代码与类本身分开。它可以放入静态工厂方法或单独的工厂类(也可以是公共静态内部类)。选择取决于代码的复杂性和设计上下文(在这种情况下我们不知道)。
这也允许你进行优化,比如缓存和重用类实例。
答案 2 :(得分:5)
我非常注重实用主义。如果它有效,那就去做吧!但是,在纯洁和善良的名义下,我想提出一个设计建议:
该课程使用检索数据内容的机制混淆了数据内容。您最终在其他地方使用的对象仅对其包含的数据感兴趣。所以“干净”的事情就是有一个不同的类来挖掘信息,然后创建这个属性对象的实例。
其他类的生命周期可能更长,因为您通常会调用方法来完成工作,而不是构造函数。 DBMSSpecInfo
的构造函数最终可能会分配一堆属性,但不会执行大量具有错误功能的数据库访问工作。
答案 3 :(得分:4)
在您的示例中,我将创建一个静态方法 GetDBMSSpecInfo(java.sql.Connection conn),它将返回 DBMSSpecInfo 对象的实例,如果出现问题则返回null(如果您不想抛出异常)。
我的DBMSSpecInfo对象不应只包含get属性:MaxIndexSize,MaxTableSize,DefaultSchema等。
我会将此对象的构造函数设为private,以便只能从静态方法创建实例。
答案 4 :(得分:3)
我不认为在构造函数中执行主要工作是个好主意,因为它没有返回值。因此,它会使错误处理变得更加复杂,因为它会迫使您使用异常。
答案 5 :(得分:2)
在构造函数中执行工作的一个缺点是不能覆盖构造函数(也不应该委托给可覆盖的方法)。
另一个是构造函数是全有或全无。如果对象包含初始化表现出独立失败的数据,则会剥夺您使用成功获取数据的能力。同样,您必须初始化整个对象,即使您只需要其中的一部分,也可能会对性能产生负面影响。
另一方面,在构造函数中执行此操作允许共享初始化状态(此处:与数据库的连接),并在之前发布。
与往常一样,在不同情况下,不同的方法更为可取。
答案 6 :(得分:2)
在构造函数中完成所有工作可能会导致“过载地狱”。您一直希望添加更多功能,而不是像在正常的面向对象开发中那样添加新方法,而是发现自己添加了越来越多的重载构造函数。最终,构造函数可以生成如此多的重载和参数,使其变得难以处理。
答案 7 :(得分:1)
请注意,对象不会被克隆/反序列化。以这种方式创建的实例不使用构造函数。
答案 8 :(得分:0)
在我看来,构造函数应该是轻量级的,不应该抛出异常。 我实现了某种Load()方法来从数据库中检索数据,或者实现延迟加载。
答案 9 :(得分:0)
没问题。 JDK有很多类在构造函数中执行网络IO。