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");
}
}
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 onNotifier
without looking at the implementation ofFoo
's methods.Its not possible to unit test the
DoStuff()
method without also testing theSendNotification
method ofNotifier
.
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");
}
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)