DEV Community

Rob
Rob

Posted on

Dependency injection explained

Dependency injection (or DI as it is often abbreviated), is the act of passing the things a class depends on to it instead of letting that class source them itself. It is one way of achieving the Inversion of Control (or IoC) principle.

Lets take an example:

public class Foo
{
    public void DoStuff()
    {
        DoTheThing();
        DoTheOtherThing();

        var notifier = new Notifier();
        notifier.SendNotification("Blah");
    }
}
Enter fullscreen mode Exit fullscreen mode

Here we have a class Foo, that uses an instance of class Notifier to send a notification. We say that Notifier is a dependency of Foo; Foo needs it to complete its work.

So what is wrong with this code?

  • Its not possible to know Foo depends on Notifier without looking at the implementation of Foo's methods.

  • Its not possible to unit test the DoStuff() method without also testing the SendNotification method of Notifier.

How does DI help us here?

  • DI makes dependcies explicit in the signature of a class's constructor or method calls.

  • DI allows us to easily pass in mock instances of dependencies during unit testing.

Let's apply DI to our earlier example:

public class Foo
    private readonly INotifier _notifier;

    public Foo(INotifier notifier)
    {
        _notifier = notifier
            ?? throw new ArgumentNullException(nameof(notifier));
    }

    public void DoStuff()
    {
        DoTheThing();
        DoTheOtherThing();
        _notifer.SendNotification("Blah");
    }
Enter fullscreen mode Exit fullscreen mode

The first thing to observe is the notifier dependency is expressed as an interface. Classes should depend on abstractions not concrete types. We have made the INotifer dependency something that is passed to the class Foo instead of created by it. In this instance we have done it via the constructor. This is known as constructor injection.

It opens up a lot of flexibility for us as programmers. Firstly we can see from the constructor what dependencies this class has. Secondly we can vary the implementation we pass to the class, for example when we do unit testing, or as the result of some configuration option.

So what creates the INotifier instance that is passed to Foo? The client code that uses Foo is now responsible for that. So havent we just moved the problem? No. The client code is free to define it's dependencies via injection as well.

Taken to its logical conclusion then, the entry point to your program is now responsible for newing up all the dependencies in your program. This point in your code is known as a composition root.

Manually newing up all this can be tedious and error prone. It is here that the use of a dependency injection container is useful. A container allows you to register dependencies with it, and takes over the role of newing up things as they are requested.

Asp.Net Core is a good example of this in the .NET world. The container is configured at application startup. Dependencies are passed into your controller instances via their constructor, and so on down the classes.

DI is a very common technique in static languages. It allows great flexibility in configuring the behaviour of your application. It also helps us to unit test our code.

Top comments (0)