I'll preface the long question with the short version of the question:
Short version of question
What is wrong with allowing an object to instantiate its own dependencies, and then providing constructor arguments (or setter methods) to simply override the default instantiations?
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;
}
}
Long version of question
My motivation for this question is that dependency injection requires you to jump through hoops just to give an object what it needs. IoC containers, factories, service locators..... all of these introduce lots of additional classes and abstractions that complicate the API of your application, and I would argue, make testing just as difficult in many cases.
Isn't it logical that an object does in fact know what dependencies it needs in order to function properly???
If the two primary motivations of dependency injections are code re-usability, and unit testability, then being able to override default instantiations with stubs or other objects accomplishes that just fine.
Meanwhile, if you need to add a House class to your application, you ONLY need to code the House class, and not a factory and/or a DI container on top of it. Further, any client code that makes use of the house can just include the house, and doesn't need to be given a house factory or abstract service locator from somewhere up above. Everything becomes extremely straight-forward, with no middleman code, and instantiated only when it's needed.
Am I totally out of line in thinking that if an object has dependencies, it should be able to load them on its own, while providing a mechanism for those dependencies to be overloaded if desired?
Example
#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);
}
}
Now, in a very large application with many different models/data classes and controllers, I feel like having to pass the DB object THROUGH every single controller (which won't need it) just to give it to every data mapper (which will need it), is a bit smelly.
And by extension, passing a service locator or DI container through the controller, just to locate the database to then give it to the datamapper every single time, also seems a bit smelly.
Same goes for passing a factory or abstract factory through to the controller, and then having to instantiate new objects through something cumbersome like $this->factory->make('\Data\User');
seems awkward. Especially since you need to code the abstract factory class, then the actual factory that wires up the dependencies for the object you want.