Image's source
21 Dec 2015 - The Dark Side

Dependency Injection Explained, Again

tl;dr a few years ago understanding dependency injection took me much more time, that I would like to admit. This is an attempt to provide an explanation that would have taken me less time to understand.


Demeter's Principle

When you design a program you cut it in chunks, classes, functions, packages, services, in all case we will call them modules... Reusable & independent pieces of code.

Those pieces however do interact together (what's the point of reusability if you do not reuse them), independence is usually lose coupling, which means that when you reuse a module you do not want / need to know its inner working, you just know that this module is respecting a contract (an interface) about what are its functions (or classes) and what they are supposed to do. You do not need to deal with inner workings there, as soon as the provided module respects the contract your code should work.

For example, if you have a persistency layer, it should not matter wether the file are stored on a file system, a database or Amazon S3, usually you just want to store and get files back.

Single Responsibility Principle

The Single Responsibility Principle means that your modules, the ones you implement should do one thing and one thing only.

For example, your module is supposed to store a users profile picture from a remote location in your own storage system. Ideally you just want to care about doing this operation, you want a remote file retrieval module and a storage module. But what if storage module, is just using the file system and requires no configuration.

# You know that the class exists, you just initialise it.
new FileSystemStorage();

But if tomorrow, it's stored in AWS S3.

# You know that the class exists, you just initialise it, but now it takes a
# config
new S3Storage(config); # to simplify here we assume that you have the config.

# But maybe you would like to use some kind of registry (bad, because
# it is Singleton based)
$registry->get('S3Storage');

But your code should not instantiate the storage, or access differently according to what it is... It should just take a storage as argument.

Dependency Injection

Tight coupling

function importUserPicture($user) {
    $storageSystem = new S3Storage(config);

    # ... rest of the code there
}

In the above example, the storage system is tightly couple to our module, this is bad, if we want to change it we have to update it everywhere.

Using a registry

function importUserPicture($user) {
    $storageSystem = $registry->get('storageSystem');

    # ... rest of the code there
}

It is better, because now, we can modify the used storage system at a single entry point, but this looks like a global registry. Global variables are usually hidden dependencies and are introducing bugs.

But our module is still caring about getting the storage system, it should just be one of its parameters.

Just think about a case where we would like to reuse this function outside of this project, we go from one to two dependencies: the storage system and the registry system.

Injecting the dependency

function importUserPicture($storageSystem, $user) {
    # ... rest of the code there
}

This is the most simple way of of injecting a dependency: as an argument.

But should the user of our module care about providing the N dependencies it relies on?

Moving the configuration to the module

Unlike the above this breaks compatibility, we introduce a class that was not required previously.

class UserPictureImporter {

    protected $_storageSystem = null;

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

    function importUserPicture($user) {
        # ... rest of the code there
    }
}

Here the user would just get a configured instance of the UserPictureImporter.

Sorry mate, isn't it a bit too verbose there.

Yeah, definitely more lines, but just consider the following features:

  • Provide a way to import a picture for a user (the instance of our class)
  • Provide a way to build a storage system independent module (the class itself through the constructor parameters).

There is no simple way to do it.

Dependency Injection Manager

Most stuff about Dependency Injection does not get into what a Dependency Injection Manager is (which is what all dependency injection libraries provide), they stop at the above.

A Dependency Injection Manager is usually managing two things:

  • Definitions: the definitions can be implicit (deduced from your code) or explicit (i.e. configured), they are telling you how to build your components. In our example, what storage system should be provided the UserPictureImporter. Those definition often contains also extra information, like whether the component is to be stored in the registry or recreated everytime the manager is asked the component.
  • Registry: the registry is a way to store elements accross multiple calls to the manager, for example, once our storage system is built, we have to reason to rebuild it again.

Dice, a good example

To my opinion, the Dice PHP library is an elegant and simple dependency injection manager.

Their example code snippet is quite explicit:

<?php
class A {
    public $b;

    public function __construct(B $b) {
        $this->b = $b;
    }
}

class B {

}

require_once 'Dice.php';
$dice = new \Dice\Dice;

$a = $dice->create('A');

var_dump($a->b); //B object

Dice is wiring dependencies by using types, making the definition of dependencies seamless.

In this example, testing the behavior of A without B is easy, you could inject a mock of B details may vary according to your dependency injection library if you do not want to do it manually.


Fräntz Miccoli

This blog is wrapping my notes about software engineering and computer science. My main interests are architecture, quality and machine learning but content in this blog may diverge from time to time.

Ideas are expressed here to be challenged.


About me Out Of The Comfort Zone Twitter