I remember the first time I got on stage with one of my bands. All the rehearsals and practices came down to one moment when we all needed to work together as one. Sounds from our instruments should merge into one, and if either one of us makes a mistake, the songs will not sound good. Next chance to do this correctly would be at the next gig. We couldn’t just say “Ok, Nikola screwed up that chorus right there so we will start from the beginning”. I had that same feeling once I started implementing Dependency Injection for the first time.

I was afraid that if anything goes wrong and if I make a wrong move it will be too late to pull back, and next chance to do this right would be the next gig. It felt like this huge over complication that at the same time was very critical, and making the wrong move would throw my project in a worse state.

My fears were unfounded, of course. Like any other technique that should be mastered, it took time and practice, but the benefits were huge. And it wasn’t that hard to begin with. All it took was using different tools, rules, and principles and make them work as one. So, what is Dependency Injection and what can we accomplish by using it?

Dependency Injection

It is going to sound like a cliché, but Dependency Injection is more a state of mind than a technique. It is the way we think about our software. Sure, we have to follow certain rules to achieve it, but the mindset is key here. We want our code to be loosely coupled. That is the goal and the mindset. Dependency Injection is the tool we will use to achieve a loosely coupled code.

We could oversimplify this principle and say that it is describing how dependencies will be injected in one component and not allow that component to find or create that dependencies itself. That would be somewhat correct, but not entirely true. To come to that point we need to follow some other principles that would make this possible – kind of like my band so many years ago, we need to follow certain rules achieve harmony.

Principles

Let’s check out all the principles that we need to integrate into our implementation so we could actually inject dependencies.

Inversion of Control

There was this moment of confusion in our community when there was no clear line between Dependency Injection and Inversion of Control. These terms were used as synonyms even though they are not. Initially, Inversion of Control (IoC) was used to refer to any sort of development where an overall framework or runtime controlled the program flow.

So, according to this definition, if you are writing ASP.NET application, you are essentially using IoC, since you were not in control of the programme flow, but the ASP.NET is. Since people started to refer to frameworks that manage dependencies as IoC Containers, the meaning of IoC evolved towards – Inversion Of Control For Dependencies. Martin Flower introduced the term Dependency Injection to distinguish this type of IoC. To sum it up, Dependency Injection is a subset of Inversion of Control that deals with managing dependencies.

Dependency Inversion Principle

It was just a matter of time before we mention SOLID principles. These principles were defined sometime in the early 2000s by Robert C. Martin and they are the backbone of Object-Oriented Design. Every letter stands for one principle and in this article, we will explore some of them. Last but not least, the principle in this set of principles is Dependency Inversion Principle. As you might already know, this principle is composed of two rules. The first rule is:

High-level modules should not depend on low-level modules. Both should depend on abstractions.

And the second rule goes like this:

Abstractions should not depend on details. Details should depend on abstractions.

For example, let’s say we are developing a blog implementation. Our blog can have regular articles or the articles that are linking similar articles called series. On top of that, let’s say that Article and Series have method Expand that needs to expand summary to full content. Hence, our Blog would have dependencies on Article and Series implementation, and that would look like something like this:

How can we implement this system to follow Dependency Inversion Principle? Take a look at this Blog class implementation:


public class Blog
{
private readonly ICollection<IBlogItem> _items;
public Blog(ICollection<IBlogItem> items)
{
_items = items;
}
public void Expand()
{
foreach (var item in _items)
{
item.Expand();
}
}
}

view raw

Blog.cs

hosted with ❤ by GitHub

As you can see we introduced the IBlogItem interface. This interface is used to abstract Articles and Series, meaning it is the abstraction that modules on higher and lower levels of abstraction will depend on. They will not depend on each other. Here is how that interface and the implementations of Article and Series look:


public interface IBlogItem
{
void Expand();
}
public class Article : IBlogItem
{
public void Expand()
{
// Article expand implementation.
}
}
public class Series : IBlogItem
{
public void Expand()
{
// Series expand implementation.
}
}

view raw

IBlogItem.cs

hosted with ❤ by GitHub

Liskov Substitution Principle

In the previous example, where we replaced the concrete implementations of Article and Series with an abstraction of IBlogItem, we unintentionally followed another SOLID principle – Liskov Substitution Principle. This principle goes hand in hand with Dependency Inversion Principle and it has quite a mathematical definition:

Let q(x) be a property provable about objects of x of type T. Then q(y) should be provable for objects y of type S where S is a subtype of T.

What does this mean? Well, it means that subtypes must be substitutable for the base type. This gives us the ability to replace one end without changing the other, meaning that if we provide a correct interface, we can change concrete implementation. That is exactly what we have done with IBlogItem. We can see how this concept is important for our dependencies.

Single Responsibility Principle

We kind of reversed the order right there and started from the last principle of the SOLID principles. The first one of them is Single Responsibility Principle (SRP). You can read one long post on this principle right here. To sum it up, the idea behind it is that every class should be responsible for one thing and one thing only. To be more exact, its full definition would go like this:

The Single Responsibility Principle (SRP) states that each software module should have one and only one reason to change.

Separating responsibilities from one another and finding a way for them to coexist is much of what software design is really about.

What does this mean for our dependencies? Well, when we know what exactly dependency needs to do, it is easy to choose correct one.

Dependency Injection Example

How about a little code that can demonstrate this principle that we talk so much about? Let’s consider a simple example; we have a Client class that has a dependency on Service class. Service class has one method – ServiceMethod(). We want to call this method from our Client class via InitiateServiceMethod() method. Ok, here is our Service class:


public interface IService
{
void ServiceMethod();
}
public class Service : IService
{
public void ServiceMethod()
{
Console.WriteLine("Service Method initiated");
// Service Method implementation.
}
}

view raw

Service.cs

hosted with ❤ by GitHub

Now, the wrong way to implement Client class would go something like this:


public class Client
{
public void InitiateServiceMethod()
{
var service = new Service();
service.ServiceMethod();
}
}

view raw

BadClient.cs

hosted with ❤ by GitHub

There are several mistakes here. As you can see we broke SRP, i.e. we created a new object of the Service class inside of the Client class. That is not the responsibility of this class. By doing this, we also tightly coupled Client and Service classes and made this dependency hard to change. We haven’t followed Dependency In Dependencyinciple and the Liskov Substitution Principle. This method InitiateServiceMethod(), will be hard if not impossible to test properly. There is no way to mock Service dependency. In short, this is not the way to go.

So, what is the proper way, you might ask? Here it is:


public class Client
{
private readonly IService _service;
public Client(IService service)
{
_service = service;
}
public void InitiateServiceMethod()
{
_service.ServiceMethod();
}
}

view raw

Client.cs

hosted with ❤ by GitHub

We created an interface IService and injected concrete implementation – Service through the constructor. This is one way to achieve Dependency Injection Principle. This way we followed all the principles we broke in the previous Client implementation. This method now can be easily tested, since we can mock IService easily as well.


using Moq;
using Xunit;
using ClientServiceExample;
namespace ClientServiceTest
{
public class ClientServiceTest
{
[Fact]
public void InitiateServiceMethod_NoConditions_Success()
{
var serviceMock = new Mock<IService>();
var client = new Client(serviceMock.Object);
client.InitiateServiceMethod();
serviceMock.Verify(x => x.ServiceMethod());
}
}
}

It is easy to replace IService with some other implementation, or with the mock object that we have done in our test. Note that I was using Xunit and Moq for the example above, so you must install these packages if you want that example to work.

Conclusion

Dependency Injection is one complex topic. It provides us with a powerful tool to manage our dependencies, but it requires discipline and following quite a lot of other principles in order for it to be done right. In this article, we could see how orchestrating these principles we actually re-invented this mindset and made loosely coupled code. Still, there is a lot of more regarding Dependency Injection that is left uncovered. In next article, we will see the different ways to implement this principle, as well as famous Dependency Injection Containers (IoC Containers).

Thanks for reading!


Read more posts from the author at Rubik’s Code.


Discover more from Rubix Code

Subscribe now to keep reading and get access to the full archive.

Continue reading