我应该在开发业务代码时使用IoC依赖注入吗?

时间:2014-10-06 18:23:24

标签: php design-patterns dependency-injection ioc-container

现代PHP框架(如Zend,Symfony,Phalcon)都使用DI容器,您只需传递它以访问所有框架功能。我想知道我是否应该/可以在我的业务代码中使用DI容器。让我们说我需要使用数据库访问对象和邮件程序对象,因为框架使用它们,它们已经在DI中。我可以在实例化业务类时简单地传递DI容器吗?

例如,我有一个处理数据库中用户的类,您可以将其称为我的用户模型类。现在,我只需将DI容器传递给模型类的构造函数,然后在控制器中实例化它,并且它很简单。只需将所有东西都放在DI容器中即可。

但是我即将开发一个也将使用此用户模型类的API。由于它需要一个DI容器,我需要事先知道模型的依赖关系,并用正确的容器初始化DI容器。之前,我只是将每个依赖项作为参数传递给构造函数,但是我需要知道IoC,而不需要查看参数,类的依赖性以及用于访问每个依赖项的名称是什么。例如,我需要知道应该有一个由' db'标识的PDO对象。在DI容器中。这是商业/图书馆代码的好方法吗?

我可能在这里混淆了条款,但我希望你明白这一点。

谢谢!

1 个答案:

答案 0 :(得分:1)

您正在开发什么样的代码,无论是业务逻辑还是框架逻辑(或其他类型的逻辑),这并不重要,这里的重点是如何处理类依赖。

术语业务逻辑本身非常抽象。您可以使用单个类(a.k.a domain object)表示业务逻辑,也可以将业务逻辑表示为layer

需要注意的一件事:数据库只是一个存储引擎

开发任何应用程序时,应记住可以更改数据库(或者将来可以迁移到NoSQL解决方案)。如果你这样做,那么$pdo不再是依赖。如果您的存储逻辑完全与业务逻辑分离,那么替换存储引擎将非常容易。否则,在更改它时,你最终会重写很多东西。

正确设计的架构鼓励将存储逻辑与应用程序逻辑分离。这些模式是最佳实践:数据映射器或表网关

namespace Storage\MySQL;

use PDO;

abstract class AbstractMapper
{
     protected $pdo;

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

class UserMapper extends AbstractMapper
{
    private $table = 'cms_users';

    public function fetchById($id)
    {
       $query = sprintf('SELECT * FROM `%s` WHERE `id` =:id', $this->table);
       $stmt = $this->pdo->prepare($query);
       $stmt->execute(array(
           ':id' => $id
       ));

       return $stmt->fetch();
    }

    // the rest methods that abstract table queries
}

因此,在这种情况下,对于当前存储引擎$pdo是核心依赖项,它不是框架或您正在开发的应用程序的依赖项。您应该在这里解决的下一个问题是如何自动化将$pdo依赖项传递给映射器的过程。只有一个解决方案可以利用 - 工厂模式

$pdo = new PDO();
$mapperFactory = new App\Storage\MySQL\Factory($pdo);

$mapperFactory->build('UserMapper'); // will return UserMapper instance with injected $pdo dependency

现在让我们看到明显的好处:

首先,它的可读性 - 任何看到代码的人都会得到线索,Mapper被用来抽象表访问。其次,您可以轻松更换存储引擎(如果您计划将来迁移并添加多个数据库支持)

$mongo = new Mongo();
$mapperFactory = new App\Storage\Mongo\Factory($mongo);

$mapperFactory->build('UserMapper'); // will return UserMapper instance with injected $mongo dependency
  

注意:不同存储引擎的所有映射器都应实现一个接口(API实施)

模型不应该是一个类

说到网络,我们基本上会做以下事情:

  • 呈现表单(可能看起来像联系页面,登录表单等)
  • 然后提交表格
  • 然后与我们的验证规则进行比较
  • 成功后,我们将表单数据插入存储引擎,如果失败,我们会显示错误消息

因此,当您将模型实现为类时,您最终会在同一个类中编写验证和存储逻辑,因此会紧密耦合并破坏SRP。

这个常见示例之一,您可以在Yii Framework中看到

正确的模型应该是包含应用程序逻辑的类的文件夹(参见ZF2或SF2)。

最后,你真的应该使用DiC吗?

开发代码时是否应该使用DI容器?好吧,让我们来看看这个经典代码示例:

class House
{
      public function __construct($diContainer)
      {
             //Let's assume that door and window have their own dependencies
             // So no, it's not a Service Locator in this case
             $this->door = $diContainer->getDoor();
             $this->window = $diContainer->getWindow();
             $this->floor = $diContainer->getFloor();
      }
}

$house = new House($di)

在这种情况下,您告诉您House取决于DiC,而不是明确地在门,窗和地板上。但等待我们的House真的依赖于DiC吗?肯定没有。

当你开始测试你的课程时,你也必须提供一个准备好的DiC(完全不相关)

通常人们使用Di容器以避免一直注射。但另一方面,由于大多数DI容器都是基于配置的,因此需要花费一些RAM和一些时间进行解析。

良好的架构可以没有任何Di容器,因为它利用了工厂和SRP。

如此优秀的应用程序组件应该没有任何Service Locators和Di Containers。