【问题标题】:To inject or to new?注入还是新的?
【发布时间】:2012-10-14 07:45:52
【问题描述】:

关于在另一个类中使用类对象的最佳做法是什么?在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;
    }    
}

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

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

【问题讨论】:

  • 前者;它被称为依赖注入,或 DI:使单元测试变得更容易,因为你可以模拟你的 $bar
  • 感谢大家的专业提示!
  • 为了与 DI 人群背道而驰,如果被实例化的对象是类的其余部分所依赖的容器,那么几乎没有理由使用 DI。这不适用于 PHP,因为 array 经常使用,但在强类型 OOP 语言(例如 C#)中,new Arraynew Listnew Dictionary 都很好,并且不会'不需要作为参数传递。

标签: php oop class testing object


【解决方案1】:

好的,Dependency Injection 很棒,很有帮助,saves kittens,所以我不打算详细讨论。

相反,我会建议您实现这两种解决方案

class Foo {
    private $bar;

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

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

【讨论】:

    【解决方案2】:

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

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

    下面的 C# 示例

        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 类。

    希望这会有所帮助。

    【讨论】:

      【解决方案3】:

      一般来说,出于How to Think About the “new” Operator with Respect to Unit Testing 中所述的原因,我会加入 DI 人群:

      但是依赖注入如此重要的原因在于,在单元测试中,您希望测试应用程序的一小部分。要求是您可以独立于整个系统构建应用程序的那个小子集。如果您将应用程序逻辑与图构造(新运算符)混合使用,那么除了应用程序中的叶节点之外,单元测试将变得不可能。

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

      例如,假设您的 Bar 需要 Logger,您会这样做

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

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

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

      但是,您不必总是注入东西。这取决于对象(您的 Bar)是否为 Injectable or a 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 没有意义。它只是一种数据类型。

      【讨论】:

        【解决方案4】:

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

        我只是想介绍另一种您可能不知道的流行替代方案:使用依赖注入容器

        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 使用更健壮的容器,即also available as a standalone compenent。但 Pimple 可能是您最好的选择,除非您正在开发大型应用程序。

        【讨论】:

          【解决方案5】:

          第一个。 (这种方法称为依赖注入)。

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

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

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

          这样就只能传入Bar 对象。


          依赖注入的优点

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

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

          【讨论】:

          • 大声笑非常感谢您的提示,以及未受伤的小猫的承诺。
          【解决方案6】:

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

          在选项 1 中:

          • 类相互独立
          • 类可以独立测试,使用 bar 类的模拟

          【讨论】:

          • 非常感谢,这是我一直在研究的,只是不知道有没有更好的方法。
          • 挑剔:它们不是独立的$bar 独立于 Foo,但 Foo 确实 依赖于 $bar。它被称为 dependency 注入是有原因的;)关于测试:您可以单独测试代码
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2018-06-15
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-02-10
          • 1970-01-01
          相关资源
          最近更新 更多