Dependency injection: An elegant way for decoupling

applaudo
By applaudo
2020-07-22

This article will introduce you to the core concept of dependency injection.

When I started the wonderful journey of being an iOS Developer, I remember feeling really lost when everyone around me started talking about Dependency Injection. At that time, for me, it sounded like some unreachable, indomitable and impossible idea to grasp and not to mention, use it in my code. But, I also remember the day when my eyes were opened and I began to contemplate the power this design pattern gives to your code.

I really hope that when you finish reading this blog, you can feel encouraged to try out this design pattern (DI) in your code, and thus enjoy its advantages and power. 

If you cringe when hearing the words dependency injection, or if you think that it’s a difficult pattern and it’s not meant for beginners, my friend, this blog is for you. The truth is that dependency injection is a fundamental pattern that is very easy to adopt. 

So, from the top… 

What is dependency injection?

 

 

Dependency injection is nothing more than injecting dependencies into an object instead of tasking the object with the responsibility of creating its dependencies. Is a great technique for decoupling code and making it easier to test.

The core idea is that rather than having our objects create their own dependencies by themselves, we inject them from the outside, enabling us to set them up differently depending on the situations — such as using mocks and other specific instances within our tests.

Let’s see a quick simple example of both cases: 

Dependency Injection in action

We can inject a GenericService  instance into the ViewModel instance. Even though the end result may appear identical, it definitely isn’t. By injecting the service, the view model doesn’t know how to instantiate the generic service. 

 

 

No Dependency Injection

This option tasks the ViewModel class with the instantiation of the GenericService instance. As you can see the view model is in charge of creating the GenericService instance.

 

 

This means that the ViewModel class not only knows about the behavior of the GenericService class, it also knows about its instantiation. That’s a subtle but important detail. 

Ways of Injection: 

The three most common forms to inject dependencies are the following: 

I will briefly explain the two forms I usually use. 

Initializer Injection 

The idea is that an object should be given its dependencies on initialization. The big benefit of this method is that it guarantees that our objects have everything they need in order to do their work right away. 

 

Property Injection 

Dependencies can also be injected by declaring an internal or public property on the class or structure to hold the dependency. This may seem convenient, but it adds a loophole in that the dependency can be modified or replaced. In other words, the dependency isn’t immutable. 

Many developers immediately discard this option because it’s cumbersome and unnecessarily complex. But if you consider the benefits, dependency injection becomes more appealing. 

Example: 

Let’s imagine we want to build a really cool app about SpaceX. One of the main screens will be in charge of showing all the past, current, and upcoming launches. Applying MVVM architecture on the app, assume we already have created the ViewLayer and decide to define the ViewModel this way: 

 

Using this approach, when LaunchesViewModelis instantiated, an APIRequester object will be created too. With this approach, everything will run smoothly when the user decides to request any type of launch (past, current, or future): the APIRequester will go to the API to request this information and it will be drawn in the view. 

Houston, we have a problem!

But, what will happen if for any reason the information of the launches will no longer come from the API only, but can come from a database as well? This approach has coupled our ViewModel to fetch data from the API alone. 

Let’s go a little further. Imagine we wanted to perform unit tests on the LaunchesViewModel, we find ourselves tied up to the fact that the requester should always hit the API, and we all know that testing with production data can be really dangerous ⚠️.  

But wait… 

This is when Dependency Injection comes to the rescue 🎉!

We are going to untangle this code in two steps: 

As a first step, by making use of protocols Interfaces, we will add a layer of abstraction so that all those classes that implement this protocol will be a candidate to be injected as a dependency in the ViewModel. 

 

 

As a second step, we will use one of the injection methods previously shown: the Initializer Injection; and define the requester dependency on LaunchesViewModelto RequesterType

 

By applying a little Dependency Injection magic, the consumer LaunchesViewModel is not rigidly coupled to just one type of Requester anymore 🤯! 

We can inject now a dependency that uses an API for fetching data about launches: 

 

Or if necessary, we can inject a dependency that goes to a Database for retrieving the data about all launches: 

 

Now the consumer LaunchesViewModel does not care about how to instantiate their dependency, not to mention where it comes from. And the best part is that the core functionality of the consumer is not affected at all! 

Benefits

  1. Separation of Concerns. 

As you may have noticed, this pattern is very simple and has one prominent principle: separation of concerns. We remove responsibilities from the consumer of the dependency: what concrete implementation to use, how to configure it, and how to manage its lifetime. This lets us easily substitute dependencies in different contexts or in tests. All without changing its consumers. That makes consumers decoupled with their dependencies, making them easier to reuse, extend, develop, and test. 

  1. Testing. 

When we are testing our objects, we can easily set up unit tests and replace our dependency for another property of its type. This allows us to create mock objects and isolate the behavior in a way that allows us to understand the behavior of our code. With dependency injection, our class, or struct, becomes much more flexible in different contexts and does not have to change any of its internal code. 

 

 

I hope that this blog has been useful to you, and made you feel encouraged to use this pattern in your code. We explored only the tip of the iceberg of this wonderful tool to decouple the code!