最佳实践:重写__construct()与提供init()方法的区别

28

当你在子类化对象并想要扩展初始化代码时,有两种方法。一种是覆盖__construct()方法,另一种是实现一个初始化方法,让你的超类构造函数调用它。

方法1:

class foo
{
    public function __construct ($arg1, $arg2, $arg3)
    {
        // Do initialization
    }
}

class bar extends foo
{
    public function __construct ($arg1, $arg2, $arg3)
    {
        parent::__construct ($arg1, $arg2, $arg3);
        // Do subclass initialization
    }
}

方法2

class foo
{
    public function init ()
    {
        // Dummy function
    }

    public function __construct ($arg1, $arg2, $arg3)
    {
        // Do subclass defined initialization
        $this -> init ();
        // Do other initialization
    }
}

class bar extends foo
{
    public function init ()
    {
        // Do subclass initialization
    }
}
Zend Framework的文档似乎不鼓励重写构造函数,而是建议在提供init方法时重写该方法,但这种方式并不让我感到舒适。另外,Zend还做了一些我不满意的事情,所以我不确定它是否应该被用作最佳实践的例子。个人认为第一种方法才是正确的,但我已经看到第二种方法被频繁使用,导致我开始怀疑是否应该采用第二种方法。
你对覆盖__construct有什么评论吗?我知道你必须小心记得调用超类构造函数,但大多数程序员应该都知道这一点。
编辑:我没有使用Zend,我只是使用它作为一个鼓励使用init()而不是覆盖__construct()的代码库的例子。
5个回答

18

看起来第二种方法只是推迟了问题。

如果你有一个类:

class bar2 extends bar // which already extends foo
{
  public function init()
  {
    // You should then do anyway:
    parent::init();

    // ...
  }
}

我也会选择第一种方法,因为它更合乎逻辑和直接,由于无法无限制地避免parent::init()parent::__construct()调用。在我看来,第一种方法不那么容易混淆。


9
我能想到只有两种情况使用init()是有意义的:当您的构造函数是非公共的,但您需要让人们有机会影响初始化,例如在抽象单例中(您不想使用它)。或者像Zend Framework一样,当需要延迟额外初始化时(但这时您不从构造函数调用init())。
顺便说一下,从超类调用子类中的方法称为模板方法。 UseCase是编排某个工作流程,但允许子类型影响其中的某些部分。虽然通常是从常规方法中完成的,请注意,您的构造函数不应编排任何操作,而只是将对象初始化为有效状态
您绝对不应该从构造函数中调用/提供init(),以防止开发人员必须记住调用超类型构造函数。虽然这听起来很方便,但它会迅速混乱继承层次结构。还要注意,它偏离了通常初始化对象的方式,开发人员必须学习这种新行为,就像他们必须学习调用超类型的构造函数一样。

我不想在编程中使用单例和注册表等类似的东西,因为我在工作中已经遇到了足够多的问题,而且这并没有让我的生活变得更轻松 :) 延迟初始化解决方案看起来可能是我唯一真正有效使用这种方法的方式,即使这样,我也觉得应该谨慎使用。谢谢你的指引。 - GordonM
顺便说一句,我猜测你现在并不真的需要声望值,所以其他人的答案被采纳了。 :) - GordonM
@meh,我还以为最有帮助的答案会得到绿色的勾勾:P - Gordon
哦,来吧,你现在应该知道互联网上都是圈子的。 :) 说真的,我接受的答案提出了一个很好的观点。你的观点也很好(所以点赞),但需要一些空闲时间来阅读所有链接。 - GordonM

3

首先

Zend 也倾向于做一些我不满意的事情

你可以简单地解决这个问题,不使用它。

但更重要的是,你应该覆盖 init() 而不是 __construct(),因为 init() 是 Zend 使用的分发操作的一部分,使用它确保了你的应用程序的其他部分已经就位。否则会破坏 Zend 的 MVC 模型的流程,并可能导致奇怪的行为。

编辑

我认为主要原因是它防止其他开发人员进行修改。你可以在 init() 中做任何事情,但不能在 __construct() 中这样做,因为需要正确运行并传递所有正确的参数。

这是来自 Zend 文档的说明:

虽然你可以随时重写动作控制器的构造函数,但我们不建议这样做。Zend_Controller_Action::_construct() 执行了一些重要任务,例如注册请求和响应对象以及从前端控制器传递的任何自定义调用参数。如果必须重写构造函数,请确保调用 parent::_construct($request, $response, $invokeArgs)。


抱歉,我在问题表述上应该更加清晰。我没有使用Zend,我正在编写一些计划在未来公开发布的代码,如果我觉得它可能有用的话。我只是以Zend作为例子,因为它倾向于使用init方法,如果一个主要的框架这样做,那么一定有原因。(即使我个人不太喜欢这个框架) - GordonM
好的,可能值得编辑一下并将其放在你的问题中。我已经编辑了我的回答。 - Jake N

1
我会使用init函数,因为如果你重写构造函数,你(通常)必须记得在子类构造函数的顶部调用父类构造函数。虽然你可能知道这一点,但你不能保证另一个负责维护你的应用程序的开发人员也会这样做。

0

使用init()而不是__construct()的一个好处是:如果构造函数的签名发生变化,您将不得不更新每个派生类以匹配新的签名。

如果init()没有参数,这种情况就不会发生。


1
如果您有一个带有init()实现的类被子类化,那么这也是正确的吗?子类也必须更新以反映超类init方法的更改吗? - GordonM

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接