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


Should I use IoC Dependence Injector when developing Business code?

现代PHP框架(如Zend、Symfony、Phalcon)都使用DI容器,您只需将其传递出去即可访问所有框架功能。我想知道我是否应该/可以在我的业务代码中使用DI容器。假设我需要使用一个数据库访问对象和一个mailer对象,它们已经在DI中了,因为框架使用了它们。在实例化业务类时,我可以简单地传递DI容器吗?

例如,我有一个处理数据库中用户的类,您可以将其称为我的用户模型类。现在,当在控制器中实例化DI容器时,我只需将其传递给模型类的构造函数,这很简单。只需将所有内容放入DI容器中即可。

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

我可能混淆了这里的术语,但我希望你能明白。

谢谢!

开发什么类型的代码并不重要,无论是业务逻辑还是框架逻辑(或另一种逻辑),这里的重点是如何处理类依赖关系。

术语业务逻辑本身是非常抽象的。您可以用单个类(也称为域对象)表示业务逻辑,也可以将业务逻辑表示为层。

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

在开发任何应用程序时,都应该记住数据库是可以更改的(或者将来可以迁移到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。

因此,好的应用程序组件应该没有任何服务定位器和Di容器。