Tuesday, February 23, 2010

The 50000 feet view and Dependency Injection

One criticism of Dependency Injection is the supposed unnecessary abstraction over collaborators that it is imposed by the constructor and setter injection techniques. Writing:
class Computer
{
    public function __construct(Cpu $cpu) {
        $this->_cpu = $cpu; 
    } 
}
instead of:
class Computer
{
    public function __construct() {
        $this->_cpu = new AmdCpu(); 
    } 
}
is said as being too abstract because a new programmer that starts reading the source code of the Computer class does not know what collaborator concrete class is used by Computer and does not know where to look for the code defining its behavior. In general, he just can't draw a picture of the overall object graph since the links between objects are scattered in many different classes and interfaces. A 50000 feet view of the system (a representation at the same level of detail of viewing a city from a plane) is difficult to grasp just from the method signatures of a highly decoupled system.
Fortunately Dependency Injection actually is about separating the construction of the object graph from the business logic, and the seams that define how classes work together are left abstract by design. Someone must construct the application or its components anyway, but the process is well encapsulated without the involved collaborators knowledge. The construction process is so decoupled from the system that it can take advantage of a DI container without introducing further coupling.
Thus in a well-written application there is already a nice 50000 feet view of the system, being it kept in a factory class or in the configuration of the DI container. A developer starting to work on a component should look at the code that constructs it in the first place.
For instance, the SpecificationLoader component of NakedPhp is a Facade composed of many different classes. The NakedPhp\Reflect\ReflectFactory class contains a createSpecificationLoader() method:
    /**
     * @return SpecificationLoader
     */
    public function createSpecificationLoader($folder, $prefix)
    {
        if (!isset($this->_specLoader)) {
            $this->_specLoader = new PhpSpecificationLoader(
                new PhpSpecificationFactory(
                    new FilesystemClassDiscoverer($folder, $prefix)
                ),
                new PhpIntrospectorFactory(
                    new FactoriesFacetProcessor(array(
                        new FacetFactory\PropertyMethodsFacetFactory,
                        new FacetFactory\ActionMethodsFacetFactory
                    )),
                    new ProgModelFactory(
                        new MethodsReflector(
                            new DocblockParser
                        )
                    )
                )
            );
        }
        return $this->_specLoader;
    }
}
This is a very practical 50000 feet view: it does not involve diagrams; it expresses dependencies between all the classes that constitute the component at the same time. Moreover, it is automatically kept synchronized with actual code refactoring since there is no external documentation involved (Code is the design). Dependency Injection is not snake oil: it's the best practice you can apply to object-oriented code.

3 comments:

marsbomber said...

Hi Giorgio,

Very nice post again!

Since you mentioned DI container in the post. I have a question about it.

I have put up a ZF and Symfony DI Container integrated sample app on github myself (http://github.com/marsbomber/zf-with-doctrine). I really like to get some advise from you in terms of what and how a DI container can be used in a ZF application.

Thanks

Giorgio said...

Imho you should extend the container resource list to include also Doctrine seams (like Doctrine_Table instances for your relevant models), so that you can put in test-friendly version. Another problem is inherent to Zend Framework 1.x, the controllers have an empty constructor so their testing is not trivial:
http://giorgiosironi.blogspot.com/2009/11/how-to-not-test-controllers.html

Unknown said...

"...and does not know where to look for the code..."

And then you suggest

"A developer ... should look at the code that constructs it in the first place."

That's circular logic. If the developer doesn't know where to look the developer will not know to look at the code that constructs it. I think that criticism of DI is valid.

ShareThis