有理解依赖注入的问题

时间:2011-03-31 09:06:21

标签: php oop model-view-controller dependency-injection tdd

我正在建立一个小项目,试图尽可能多地教自己,这对我来说意味着不使用预制框架(如杰夫once put it,“不要重新发明轮子, 除非您计划更多地了解车轮“[强调我的]”并遵循测试驱动开发的原则。

在我的追求中,我最近遇到了依赖注入的概念,这对TDD来说是必不可少的。我的问题是我无法完全绕过它。到目前为止,我的理解是它或多或少等于“让调用者传递它可能需要的任何其他类的类/方法,而不是让它们自己创建它们。”

我有两个我想用DI解决的示例问题。我是否在这些重构的正确轨道上?

数据库连接

我打算只使用单例来处理数据库,因为我目前不希望使用多个数据库。最初,我的模型看起来像这样:

class Post {  
  private $id;  
  private $body;  

  public static function getPostById($id) {  
    $db = Database::getDB();  
    $db->query("SELECT...");  
    //etc.  
    return new Post($id, $body);
  }  

  public function edit($newBody) {  
    $db = Database::getDB();  
    $db->query("UPDATE...");  
    //etc.  
  }  
}  

使用DI,我认为它看起来更像是这样:

class Post {  
  private $db; // new member

  private $id;  
  private $body;  

  public static function getPostById($id, $db) { // new parameter   
    $db->query("SELECT...");  // uses parameter
    //etc.  
    return new Post($db, $id, $body);
  }  

  public function edit($id, $newBody) {   
    $this->db->query("UPDATE...");  // uses member
    //etc.  
  }  
} 

我仍然可以使用单例,并在应用程序设置中指定凭据,但我只需要从控制器传递它(控制器无论如何都是单元可测试的):

Post::getPostById(123, Database::getDB);

调用模型的模型

以一个有视图计数的帖子为例。由于确定视图是否为新的逻辑并不是Post对象特有的,因此它本身就是一个静态方法。然后Post对象将调用它:

class Post {
  //...

  public function addView() {
    if (PageView::registerView("post", $this->id) {
     $db = Database::getDB();
     $db->query("UPDATE..");
     $this->viewCount++;
   }
}

使用DI,我认为看起来更像是这样:

class Post {
  private $db;
  //...

  public function addView($viewRegistry) {
    if ($viewRegistry->registerView("post", $this->id, $this->db) {
     $this->db->query("UPDATE..");
     $this->viewCount++;
   }
}

这会将来自控制器的呼叫更改为:

$post->addView(new PageView());

这意味着实例化一个只有静态方法的类的新实例,这对我来说很难闻(我觉得在某些语言中是不可能的,但是这里可行,因为PHP不允许类本身是静态的)。 / p>

在这种情况下,我们只是深入一级,所以让控制器实例化一切似乎都可行(尽管PageView类通过Post的成员变量间接获得其数据库连接),但似乎它可能得到如果你不得不调用一个需要一个类需要一个类的类的方法,那就太笨了。我想这可能只是意味着代码闻起来了。

我是否在正确的轨道上,或者我完全误解了DI?任何批评和建议都非常感谢。

4 个答案:

答案 0 :(得分:7)

是。看起来你有正确的想法。您将看到,当您实现DI时,所有依赖项将浮动到“顶部”。将所有内容放在顶部可以轻松模拟必要的测试对象。<​​/ p>

拥有一个需要一个类需要一个类的类并不是一件坏事。你在那里描述的是你的对象图。这对于DI来说是正常的。让我们以House对象为例。它依赖于厨房;厨房依赖于水槽;水槽依赖于水龙头等。众议院的实例化看起来像new House(new Kitchen(new Sink(new Faucet())))。这有助于实施单一责任原则。 (另外,你应该在工厂或建筑商这样的实例化工作中进一步执行单一责任原则。)

Misko Hevery撰写了大量有关DI的文章。他的blog是一个很好的资源。他也是pointed out some of the common flaws(构造函数真正的工作,挖掘合作者,脆弱的全球状态和单身人士,并且课程做得太多)带有警告标志来发现它们以及修复它们的方法。这值得一试。

答案 1 :(得分:4)

它肯定会朝着正确的方向发展,但你不应该就此止步。

DI的目的是消除类之间的强耦合,以便更容易地替换单个组件。这将允许更好的可测试性,因为您可以使用Mocks和Stubs更轻松地替换依赖项。一旦您的代码经过测试,就可以轻松地进行更改和维护。

因此,您还应该删除代码中那些会产生强耦合气味的其他方面,例如:删除静态方法以及单例和任何其他全局变量。

有关此问题的更多信息,请参阅


编辑:,其他几个答案建议使用DI容器,我觉得有必要强调你需要DI容器来进行DI。上面给出的最后一个链接中的第二篇博文讨论了这一点。

答案 2 :(得分:4)

依赖注入是关于注入。注入外部对象需要一些解决方案。

传统方法是:

  • 构造函数注入__construnctor($dependecy) {$this->_object = $dependency}
  • setter injection setObject($dependency) {$this->_object = $dependency}
  • gettter injection getObject() {return $this->_dependency}并开发此方法,例如。来自测试中的存根或模拟。

您也可以混合以上所有内容,具体取决于您的需求。

避免静态调用。我的个人规则是仅在您调用某些功能时使用静态,例如My::strpos()或处理单身人士或登记处时(应限制在最低限度,因为全球国家是邪恶的)。

当您的应用具有良好的依赖性容器时,您很少需要静态方法。

看一下其他dependency injection + [php] topics on SO

评论后编辑:

容器

不同的框架以不同的方式处理容器。通常,这是一个对象,它保存您需要的对象的实例,因此您不必每次都实例化新对象。您可以使用这样的容器注册任何对象,然后随时访问它。

容器可以在启动时实例化您需要的所有资源,或者在访问时延迟加载资源(更好的解决方案)。

举个例子,考虑一下:

另一个很好的参考:

答案 3 :(得分:1)

回答你的问题:是的,你走在正确的轨道上。为了给你更多详细信息:这是我发现与DI相关的最佳帖子之一:

http://www.potstuck.com/2009/01/08/php-dependency-injection

您将了解容器是什么: $ book = Container :: makeBook();

关于第二个例子:在你的方法addView中我会尽量避免传递对象$ viewRegistry,我会检查控制器外面的条件。