在阐述长问题之前,我将用一个简短版的问题来引入:
问题简述
允许对象实例化自己的依赖关系有什么问题,并提供构造函数参数(或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;
}
}
问题的详细版本
我提出这个问题的动机是因为依赖注入需要您跳过许多障碍才能为对象提供所需的内容。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);
}
}
在一个有很多不同模型/数据类和控制器的庞大应用中,我觉得必须将DB对象通过每个控制器(它们并不需要)传递,只为了将其提供给每个数据映射器(它们却需要),这有点不太合理。
而且,通过控制器传递服务定位器或DI容器,只是为了定位数据库,然后再把它传递给数据映射器,也似乎有些不太合理。
同样的情况也适用于通过工厂或抽象工厂传递到控制器,然后通过像$this->factory->make('\Data\User');
这样的繁琐方式实例化新对象似乎很笨拙。 特别是因为你需要编写抽象工厂类,然后是实际的工厂,为你想要的对象连接依赖项。