it-swarm.cn

构造函数通常不应调用方法

我向同事介绍了为什么构造方法调用方法可以是反模式。

示例(在我生锈的C++中)

class C {
public :
    C(int foo);
    void setFoo(int foo);
private:
    int foo;
}

C::C(int foo) {
    setFoo(foo);
}

void C::setFoo(int foo) {
    this->foo = foo
}

我想通过您的额外贡献更好地激发这一事实。如果您有示例,书籍参考,博客页面或原则名称,将非常欢迎他们。

编辑:我一般来说,但我们在python中编码。

12
Stefano Borini

您尚未指定语言。

在C++中,构造函数在调用虚拟函数时必须当心,因为它所调用的实际函数是类实现。如果它是没有实现的纯虚拟方法,则将是访问冲突。

构造函数可以调用非虚拟函数。

如果您的语言是Java,其中函数通常默认为虚拟的),那么您必须格外小心。

C#似乎可以按您期望的方式处理这种情况:您可以在构造函数中调用虚拟方法,并且它可以调用最最终的版本。因此,在C#中不是反模式。

从构造函数中调用方法的常见原因是,您有多个构造函数要调用通用的“ init”方法。

请注意,析构函数与虚拟方法会有相同的问题,因此您不能拥有位于析构函数之外的虚拟“清理”方法,并期望它被基类析构函数调用。

Java和C#没有析构函数,它们有终结器。我不知道Java的行为。

C#在这方面似乎可以正确处理清理。

(请注意,尽管Java和C#具有垃圾回收,但仅管理内存分配。析构函数还需要进行其他清理工作,但不释放内存)。

26
CashCow

好了,现在已经消除了关于类方法实例方法的困惑,我可以给出一个答案:-)

问题不在于通常从构造函数调用实例方法。它是直接(间接)调用virtual方法。主要原因是在构造函数内部时,对象尚未完全构造。特别是在执行基类构造函数时,根本不会构造其子类部分。因此,其内部状态在语言依赖方式上是不一致的,并且这可能会导致不同语言的细微错误。

其他人已经讨论过C++和C#。在Java中,将调用派生程度最大的虚拟方法,但是该类型尚未初始化。因此,如果该方法正在使用派生类型中的任何字段,则这些字段可能在该时间点尚未正确初始化。这个问题在 有效Java第二版 ,项目17:中进行了设计和文档设计,或者禁止继承

请注意,这是过早发布对象引用的一般问题的特例。实例方法具有一个隐式this参数,但是将this显式传递给方法会导致类似的问题。尤其是在并发程序中,如果对象引用过早地发布到另一个线程,则该线程可以在第一个线程的构造函数完成之前调用其上的方法。

18
Péter Török

我不会认为这里的方法调用本身就是反模式,更多是代码气味。如果类提供了reset方法,该方法将对象返回到其原始状态,则在构造函数中调用reset()是DRY。 (我没有对复位方法发表任何声明)。

这是一篇可以帮助您满足权威要求的文章: http://misko.hevery.com/code-reviewers-guide/flaw-constructor-does-real-work/

它并不是真正地关于调用方法,而是关于做太多事情的构造函数。恕我直言,在构造函数中调用方法是一种气味,可能表明构造函数太重。

这与测试代码的难易程度有关。原因包括:

  1. 单元测试涉及大量的创建和销毁-因此构建应该很快。

  2. 取决于那些方法的作用,如果不依赖于构造函数中设置的某些(可能不可测试的)前提条件,可能很难测试离散的代码单元(例如,从网络获取信息)。

7
Paul Butcher

从哲学上讲,构造函数的目的是将原始的内存块转换为实例。在执行构造函数时,该对象尚不存在,因此调用其方法是一个坏主意。您可能根本不知道它们在内部做什么,并且当它们被调用时,他们可能理所当然地认为该对象至少存在(duh!)。

从技术上讲,这可能没有什么问题,在C++中,尤其是在Python中,请务必谨慎。

实际上,您应该将调用限制为仅初始化类成员的此类方法。

3
user17621

这不是一个通用的问题。这在C++中是一个问题,特别是使用继承和虚拟方法时,因为对象构造是向后发生的,并且vtable指针会在继承层次结构中的每个构造函数层中重置,所以如果调用虚拟方法可能不会最终得到与您要创建的类实际对应的方法,这违背了使用虚拟方法的全部目的。

在具有理智的OOP=)支持的语言中,从一开始就正确设置了vtable指针,这个问题不存在。

2
Mason Wheeler

调用方法有两个问题:

  • 调用虚拟方法,该方法可以执行意外的操作(C++)或使用尚未初始化的部分对象
  • 调用一个公共方法(应强制执行类不变式),因为该对象不一定是完整的(因此它的不变式可能不成立)

调用辅助函数没有任何问题,只要在前面两种情况下都没有。

2
Matthieu M.

我不买这个。在面向对象的系统中,调用方法几乎是您唯一可以做的事情。实际上,它或多或少是“面向对象”的definition。因此,如果构造函数无法调用任何方法,那么can会做什么呢?

1
Jörg W Mittag

在O.O.P.理论上没关系,但是在实践中,每种O.O.P.编程语言处理不同的构造函数。我不经常使用静态方法。

在C++和Delphi中,如果必须为某些属性(“字段成员”)赋予初始值,并且代码已非常扩展,那么我将添加一些辅助方法作为构造函数的扩展。

并且不要调用其他方法来做更复杂的事情。

至于属性“ getters”和“ setters”方法,我通常使用私有/受保护的变量来存储它们的状态,以及“ getters”和“ setters”方法。

在构造函数中,我将“默认”值分配给属性状态字段,[〜#〜] without [〜#〜]调用“访问器”。

0
umlcat