17

关于在另一个类中使用类对象,最佳实践是什么?在 class _construct 语句中传递类对象还是创建一个新的类对象?

示例 1:

class Foo {
    private $bar;

    public function __construct($bar){
       $this->bar = $bar;
   }
}

或示例 2:

class Foo {

    private $bar;

    public function __construct(){
        $this->bar= NEW bar;
    }    
}

我知道很明显,类文件必须已经包含在其他地方是理所当然的,并且在第一个实例中,这种类型的类对象需要已经存在,但我想知道每种方法的优点是什么,因为我有很多类,我需要编写使用数据库对象的代码,并且我需要将其传递到类中的最佳方法。有没有比这两个更好的第三种选择?

据我了解,第一个的优点可能是少了几行编码,并且在数据库的情况下,没有创建新的连接。然而,第二个可能会更好,因为它更独立?无论如何,我想我会问专家。

4

6 回答 6

13

首先。(这种方法称为依赖注入)。

构造函数询问问题中的对象需要什么才能工作。这样,仅从方法(它们需要什么,以及它们返回什么)就很清楚它做了什么。甚至不用看源代码。

改进代码的一种方法是在方法中引入类型提示:

class Foo {
    private $bar;

    public function __construct(Bar $bar){
       $this->bar = $bar;
   }
}

这样只能Bar传入对象。


依赖注入的优点

  • 非常可读。
  • 无需查看源代码即可判断方法的依赖关系。
  • 使单元测试成为可能。
  • *将小猫从上帝的愤怒中拯救出来。

* 免责声明:在此答案的表现过程中没有小猫受到伤害

于 2012-10-24T09:31:28.760 回答
10

您应该选择选项 1,因为这是最简单的依赖注入形式。

在选项 1 中:

  • 类是相互独立的
  • 类可以独立测试,使用 bar 类的模拟
于 2012-10-24T09:30:18.300 回答
8

一般来说,出于如何考虑单元测试的“新”操作员中概述的原因,我会加入 DI 人群:

但依赖注入如此重要的原因在于,在单元测试中,您希望测试应用程序的一小部分。要求是您可以独立于整个系统构建应用程序的那个小子集。如果您将应用程序逻辑与图形构造(新操作符)混合使用,那么除了应用程序中的叶节点之外,任何东西都无法进行单元测试。

将您的代码分成创建者图和协作者图将有助于保持您的代码可维护和可测试。更好的是,针对接口编写代码,并且很容易将具体实现与其他实现交换。这使得更改代码变得简单,因为您不必费力地寻找硬编码的依赖项。

例如,假设你Bar需要一个记录器,你会做

class Foo
{
    private $logger;

    public function __construct(LogInterface $logger)
    {
        $this->logger = $logger;
    }
}

然后你传入任何实现它的具体实现LogInterface,比如 Database Logger 或 StdOutLogger 或者可能包含这两者的 Composite Logger。另一个例子是数据库对象。您可以在引导程序中创建一次,然后将其传递给使用它的对象。

如有疑问,请使用依赖注入。

但是,您不必总是注入东西。这取决于对象(您的 Bar)是Injectable 还是 Newable。引用 Misko Hevery 的话:

Injectable 类可以在其构造函数中请求其他 Injectable。[…] Injectables 往往有接口,因为我们可能不得不用对测试友好的实现来替换它们。但是,Injectable 永远不能在其构造函数中请求非 Injectable (Newable)。这是因为 DI 框架不知道如何生成 Newable。[...] Newables 的一些示例是:电子邮件、MailMessage、用户、信用卡、歌曲。如果您保持这种区别,您的代码将易于测试和使用。如果您违反此规则,您的代码将难以测试。

简而言之,当你有一些不能被合理注入的东西时,因为它是基于用户提供的或运行时信息,你可以new的。对于值对象和数据类型尤其如此:

class Foo
{
    private $storage;

    public function __construct()
    {
        $this->storage = new SplObjectStorage;
    }
}

注入没有意义SplObjectStorage。它只是一种数据类型。

于 2012-10-24T09:39:49.553 回答
0

其他人已经回答了您的问题 - 绝对采用第一种方法,它使用依赖注入。

我只是想加入另一个你可能不知道的流行替代方案:使用依赖注入容器

一个伟大而简单的例子是Pimple;由 Symfony 框架背后的人 Fabien Potencier 开发。

示例 3:

# In a bootstrap file...
require_once '/path/to/Pimple.php';

$container = new Pimple();
$container['bar'] = function ($c) {
    return new Bar(/** If bar has dependencies, put them here **/);
};

$container['foo'] = function ($c) {
    return new Foo($c['bar']);
};

# You'd still inject the service using DI, because it's not good 
# practice for your objects to rely directly on the container

class Foo {
    private $bar;

    public function __construct($bar){
       $this->bar = $bar;
   }
}

# The difference would be how you call Foo...
$foo = $container['foo'];

# So now your code doesn't need to know about the dependencies, and it's easy 
# to change them at any time by making a single change in your configuration

Symfony2 使用更强大的 Container,它也可以作为独立组件使用。但 Pimple 可能是您最好的选择,除非您正在开发大型应用程序。

于 2012-10-24T09:48:12.350 回答
0

我会说使用第一个选项。这样做时,我会说对抽象进行编程比对实现进行编程更好。

您的第一个选项是聚合形式,而第二个选项是组合形式。使用抽象可以获得的好处是,使用 FOO 类的客户端类将能够让 FOO 根据它决定发送到构造函数的接口的特定实现来执行某些活动。

下面的 AC# 示例

    class Foo {
    private IBar bar;

    public Foo(IBar obj){
       this.bar = obj;
   }

   public void myFooMethod()
   {
      bar.ExecuteMethod();
   }
}

调用 FOO 的类

    public class MyCallingClass
    {

       public void CallFooMethod()
       {
          IBar bar1Obj = new BAR1();
          Foo fooObject = new Foo(bar1Obj);
          fooObject.ExecuteMethod();
//or
          IBar bar2Obj = new BAR2();
          fooObject = new Foo(bar2Obj);
          fooObject.ExecuteMethod();
//or
          IBar bar3Obj = new BAR3();
          fooObject = new Foo(bar3Obj);
          fooObject.ExecuteMethod();

       }

    }

现在我的调用类可以将 IBar 的任何实现传递给 FOO 类。

希望这有帮助。

于 2012-10-24T14:37:44.317 回答
0

好的,依赖注入非常棒,而且很有帮助并且可以拯救小猫,所以我不打算详细讨论它。

相反,我将建议您实施这两种解决方案

class Foo {
    private $bar;

    public function __construct($bar = null){
       $this->bar = isset($bar) ? $bar : new Bar();
   }
}

这样,您可以默认使用默认类,如果您需要更改功能,您也可以这样做。

于 2012-10-24T14:55:32.133 回答