The not-so-thin line between DI, DIP and IoC

0

Precision is a basic requirements for effective communication. Software engineering is not an exception. That’s why establishing clear terminology is vital. When each party uses the same term to describe a different concept, chaos will rise. One common example in the domain of software engineering where such confusion often exists is the distinction between Dependency Injection, Dependency Inversion and Inversion of Control. In this post, I will try to make this distinction apparent by describing the goals and the semantics for each one of these three. I will start with some “definitions” and then I will go on with very basic examples that highlight the essence of each term.

Dependency Injection (DI) is about how an object gets a reference to its dependencies. How it gets a reference to another object it needs to use. It tries to separate the creation of an object from its use. But the important thing is how it tries to do that. There is no requirement by DI to use an abstraction as a representation of the object you want to use. So, DI does not say that you don’t know which class should the dependency object belong to. DI says that in order to use an object we don’t need to know how to configure it (I am using the word ‘configuration’ here in a more generic sense including the creation of the object and  handling the dependencies of that object) and so we don’t want to be responsible for configuring it. That means…decoupling. DI decouples the configuration of a dependency object from its use. You can exchange the word “configuration” for construction, if you wish. In general, DI decouples whatever comes before the usage of the dependency object (construction, configuration, preparation,…put what ever you want here) from its usage.

DI can be applied only when the configuration/initialization of the dependency can be done globally (is not dependent on the use case). In such a case, the initialization of the dependency is not bind to any intermediate results.

One of the first articles referring to DI was an article written by Martin Flower in 2004 [1]. This article is being used many times as a reference to the definition of DI. The problem with this article is that it tries to explain the dependency injection technique after placing it in a wider context that includes Inversion of Control and Dependency Inversion at the same time. When Martin describes the basic idea of DI by saying “The basic idea of the Dependency Injection is to have a separate object, an assembler, that populates a field in the lister class with an appropriate implementation for the finder interface..” , it is already too late. This description is not an exact and accurate definition of DI. It has been heavily affected by all the decisions that Martin has already made while building his example (using an interface, making the dependency passed to the lister with the initiative of an external component,…). And this is the reason why someone (especially a junior developer) may have a hard time to understand DI from this article.

Dependency Inversion Principle (DIP) is about decoupling the high-level components from the implementation of low-level components (dependencies). It urges for abstraction of low-level components because the high-level component only cares about what behavior the low-level component should provide and the semantics of this behavior. It shouldn’t care about how this behavior is being implemented or which implementation will be used (if there are more than one implementations). This is achieved by using abstractions. The higher-level component should expect a low-level component that implements a specific interface, a specific behavior. It doesn’t care which exactly is that low-level component. So, DIP decouples the construction of an object from its use. The high-level component should not know how to construct the object. The construction should be delegated to a class that is designated for this. This can be achieved through many ways:

(a) it can be injected from outside by the one that creates or uses the high-level component (from an even higher-level component). In this case, the initiative to ask for the dependency does not come from the one that uses it. This injection usually takes place through the constructor or a setter method (see Dependency Injection).

(b) It can be directly retrieved by a class that is designated for building the dependency (e.g a factory or a service locator). The initiative here belongs to the class that uses the dependency.

(c) It can be “injected” as a trait.

Inversion of Control (IoC) is a technique that allows us to separate reusable parts of our application (e.g a framework) from the tailored code (the business logic) in such a way that the first is the one that calls the second and not the opposite. IoC urges for the construction of a skeleton (you can call me “framework”) that handles the flow of control. The framework is the one that will call the business logic whenever it seems appropriate. The parts of the code that implement the business logic work as plugins to the extensible framework.

“The methods supplied by the user tailor the generic algorithms defined in the framework for a particular application” (Ralph Johnson and Brian Foote)

One more distinction: Given these “definitions” we can also make an additional distinction between a DI container and a service locator. DI is the application of the IoC principle to the dependency management. It says that the class does not need to explicitly ask for the object it needs to do its job but any required object will be passed to it by the framework or, generally, a higher-level component. And this is the difference between a DI container and a container that works as a service locator. The first sends the dependencies to a class by its own initiative (e.g by auto-wiring). In the second case, the class has to explicitly ask the container to send the dependency.

Let’s take a look at some examples.

class FileLogger
{
    private $logFile;

    public function __construct(string $logFile) {
        $this->logFile = $logFile;
    }

    public function log(string $message) {
        // ...
    }
}

class UserManager {
    private $logger;

    public function __construct()
    {
        $this->logger = new FileLogger('log.txt');
    }

    public function register($username, $password) {
        //...

        $this->logger->log('...');
    }
}

As we can see, the UserManager class has a dependency on the FileLogger class. Potential problems with this hard-coded dependency are:

– In order to change the logger we need to modify the UserManager in all the places it is being used because there is no re-assurance that the new logger will have the same method signatures.

– We cannot make an isolated unit test for the UserManager. We need to engage the FileLogger and we expect it to behave good.

– The UserManager needs to know more than the communication interface of the logger. It needs to know how to construct and configure the logger.

Now let’s try to apply the DIP. As we have said, it can be done through many ways. I will use an oversimplified static factory just for the sake of our example. We don’t really care about the details of the factory pattern here.

interface Logger {
    public function log(string $message);
}

class FileLogger implements Logger
{
        // ...
}

class DatabaseLogger implements Logger
{
        // ...
}

class LoggerFactory {
    pubic static function getLogger() : Logger {
        // it can be a new logger every time or the same singleton

        // configure it as you want here
    }
}

class UserManager {
    private $logger;

    public function __construct()
    {
        $this->logger = LoggerFactory::getLogger();
    }

    public function register($username, $password) {
        //...

        $this->logger->log('...');
    }
}

As you can see, the instantiation and configuration of the logger instance is not anymore the responsibility of the UserManager but it is being handled by a class that is specifically designated for this. What is more, the UserManager class (high-level component) now depends on an abstraction. It doesn’t know and it doesn’t care how the logger (low-level) component is being implemented. it can be a FileLogger, a DatabaseLogger or any other implementation. In any case, no IoC is applied here. The UserManager explicitly asks for the dependency.

Let’s see an alternative way to implement the DIP:

function getContainer(){
    // Return a container which implements the service locator pattern.
}

class UserManager {
    public function register($username, $password) {
        //...

        $logger = getContainer()->getLogger();
        $logger->log('...');
    }
}

Still, , the UserManager class depends on an abstraction and it just uses the logger. It is not involved in the creation or configuration of the logger.

Let’s go back to the original example and let’s now try to apply the dependency injection technique.

class UserManager {
    private $logger;

    public function __construct(FileLogger $logger)
    {
        $this->logger = $logger;
    }

    public function register($username, $password) {
        //...

        $this->logger->log('...');
    }
}

In this case, a higher-level component passes a logger instance to the UserManager during construction. Beside the injection, you can clearly see that IoC is being applied here. The UserManager does not explicitly ask for the dependency. Of course, we cannot see the rest of the code, but if, for example, auto-wiring is being used then the framework is the one that sends the logger object to the UserManager.

If you want to elaborate more on the subject I could recommend the following sources of information :

[1]. Dependency injection (wikipedia)
[2]. Inversion of Control Containers and the Dependency Injection pattern , by Martin Fowler
[3]. Inversion Of Control , by Martin Fowler
[4]. “Clean Code: A Handbook of Agile Software Craftsmanship”, Robert C. Martin
[5]. DIP in the Wild , by Brett L. Schuchert