依赖性覆盖而不是依赖注入?

Agm*_*her 10 php oop dependency-injection

我将在问题的简短版本前面提出长问题:

问题的简短版本

允许对象实例化自己的依赖项,然后提供构造函数参数(或setter方法)来简单地覆盖默认实例化有什么问题?

class House
{
   protected $door;
   protected $window;
   protected $roof;

   public function __construct(IDoor $door = null, IWindow $window = null, IRoof $roof = null)
   {
      $this->door   = ($door)   ? $door   : new Door;
      $this->window = ($window) ? $window : new Window;
      $this->roof   = ($roof)   ? $roof   : new Roof;
   }
}
Run Code Online (Sandbox Code Playgroud)

长版问题

我对这个问题的动机是依赖注入要求你跳过箍只是为了给对象提供它需要的东西.IoC容器,工厂,服务定位器......所有这些都引入了许多额外的类和抽象,使应用程序的API变得复杂,我认为,在许多情况下使测试同样困难.

事实上,对象确实知道它需要什么依赖才能正常运行,这是不合逻辑的

如果依赖注入的两个主要动机是代码可重用性和单元可测试性,那么能够使用存根或其他对象覆盖默认实例化就可以完成.

同时,如果你需要在你的应用程序中添加House类,你只需要编写House类,而不是它上面的工厂和/或DI容器.此外,任何使用房屋的客户代码都可以包括房屋,并且不需要从上面的某个地方获得房屋工厂或抽象服务定位器.一切都变得非常简单,没有中间人代码,只有在需要时才会实例化.

我完全不认为如果一个对象有依赖关系,它应该能够自己加载它们,同时提供一种机制让这些依赖关系在需要时被重载?

#index.php (front controller)

$db = new PDO(...);
$cache = new Cache($dbGateway);
$session = new Session($dbGateway);
$router = new Router;

$router::route('/some/route', function() use ($db, $cache, $session) 
{   
   $controller = new SomeController($db, $cache, $session);
   $controller->doSomeAction();
});



#SomeController.php

class SomeController
{
   protected $db;
   protected $cache;
   protected $session;

   public function __construct(PDO $db, ICache $cache, ISession $session)
   {
      $this->db = $db;
      $this->cache = $cache;
      $this->session = $session;
   }

   public function doSomeAction()
   {
      $user = new \Domain\User;
      $userData = new \Data\User($this->db);

      $user->setName('Derp');
      $userData->save($user);
   }
}
Run Code Online (Sandbox Code Playgroud)

现在,在一个包含许多不同模型/数据类和控制器的非常大的应用程序中,我觉得必须通过每个控制器(不需要它)来传递DB对象,只是为了将它提供给每个数据映射器(这将需要)它),有点臭.

通过扩展,通过控制器传递服务定位器或DI容器,只是为了定位数据库然后每次都将它提供给数据映射器,也似乎有点臭.

将工厂或抽象工厂传递给控制器​​也是如此,然后必须通过繁琐的东西来实例化新对象,就像$this->factory->make('\Data\User');看起来很尴尬.特别是因为您需要对抽象工厂类进行编码,然后是实际的工厂,它将所需对象的依赖关系连接起来.

Mar*_*arc 5

你的问题问得很好,我真的很喜欢人们出于“单元测试和可维护性”的原因质疑常识性的东西(无论这些中的哪一个,如果你不做,你就是一个糟糕的程序员-它-topics,它总是对单元测试和可维护性)。所以您在这里提出了正确的问题:DI 是否真的支持单元测试和可维护性,如果是,如何支持?并预测它:如果使用得当...

关于分解

依赖注入(DI)和控制反转(IoC)是机制,它们增强了OOP的封装和关注点分离的核心概念。因此,要回答这个问题,必须争论为什么封装和关注点分离是很酷的事情。两者都是分解的核心机制:封装(是的,我们有模块)和关注点分离(我们有合理的模块)。关于这个主题可以写很多,但就目前而言,只要说它是关于降低复杂性就足够了。系统的分解使您可以将系统(无论系统有多大)分解为人脑能够管理的块。虽然有点哲学,但这真的很重要:如果没有人脑的限制,整个可维护性主题就不会 没那么重要。好的,让我们说:分解是减少系统的感知复杂性分成我们可以管理的块。

但是,一如既往,它是有代价的:分解也增加了复杂性,正如您在关于 DI 时所说的那样。那么它还有意义吗?是的,因为:

人工添加的复杂性是独立于系统的固有的复杂性。

在抽象层面上,基本上就是这样。它具有以下含义:您需要根据您正在构建的系统的固有复杂性(或某天可能达到的复杂性)来选择分解程度和实现分解的程度。

用 DI 分解

特别是关于 DI:根据上述内容,存在足够小的系统,其中 DI 增加的复杂性并不能证明减少的感知复杂性是合理的。而且,不幸的是,网络上的每一个教程都涉及其中一个不支持理解整个模糊的内容。

然而,大多数(或至少许多)现实生活中的项目都达到了一定程度的内在复杂性,额外分解的投资得到了很好的利用,因为感知复杂性的降低加速了后续开发并减少了错误。依赖注入是这样做的技术之一:

DI 支持What(接口)和How 的分离(实现):如果只是关于玻璃门,我同意:如果这对一个人的大脑来说太过分了,他或她可能不应该成为一名程序员。但现实生活中的事情要复杂得多:DI 可以让你专注于真正重要的事情:作为房子,我不在乎我的门,只要我能依靠它可以关闭和打开的事实。也许现在没有任何门?在这一点上你根本不需要关心。在你的容器中注册组件时,你可以再次关注:我的房子里想要什么门?您不再需要关心门或房子本身:它们很好,您已经知道了。您已经分离了关注点:事物如何组合在一起(组件)和实际将它们组合在一起(容器)的定义。就这样,据我所知,从我的经验来看。这听起来很笨拙,但在现实生活中,这是一项了不起的成就。

少一点哲理

为了让它再次脚踏实地,还有一些更实际的优势:

在系统不断发展的过程中,总会有一些部分尚未开发。在大多数情况下,指定一个行为比实现它要少得多。没有 DI,只要没有开发门,您就无法开发您的房子,因为没有任何东西可以实例化。使用 DI,你不在乎:你设计你的房子,只用接口,你用模拟为这些接口编写测试,你的罚款:你的房子工作,甚至没有门窗。

您可能知道以下内容:您已经为某事(比如玻璃门)工作了好几天并且您感到自豪。六个月后——你在此期间学到了很多东西——你再看一遍,它是废话。你扔掉它。没有 DI,你需要改变你的房子,因为它使用你刚刚丢弃的类。使用 DI,您的房子不会改变。它可能位于它自己的程序集中:您甚至不需要重新编译内部程序集,它不会被触及。在复杂的场景中,这是一个巨大的优势。

还有更多,但也许考虑到所有这些,当您下次阅读它们时,更容易想象 DI 的好处......