Dependency injection — keep it easy

Ilia
5 min readFeb 10, 2019

--

When I’ve heard this term for the first time and went googling it all ended up in even more questions: what is Inversion of control or IoC, IoC containers, coupled code and its decoupling, all these Spring, Guice … frameworks. They can confuse almost everyone from the very beginning. That’s why I would like to explain the principle of dependency injection (DI) and those concepts listed above in the simplest way possible. Before we start here is an amazing and very high-level explanation of the motivation behind DI usage:

Dependency injection for five-year-olds

When you go and get things out of the refrigerator for yourself, you can cause problems. You might leave the door open, you might get something Mommy or Daddy doesn’t want you to have. You might even be looking for something we don’t even have or which has expired.

What you should be doing is stating a need, “I need something to drink with lunch,” and then we will make sure you have something when you sit down to eat.

John Munsch, 28 October 2009

Example

For the beginning take a look at this naive example:

So, what do we have here? At first, we have some lazy student who is preparing for his philosophy exam at the very last day and is in a desperate need for some powerful search through a given book by keywords in exam questions. The student is using search service implementation which is able to find only exact matches and returns page numbers as a result. In order to run this “situation” we can do the following:

So far so good. Now let’s imagine that this lazy student doesn’t want to use the exact match search strategy anymore. He is better prepared now and for him “like” search strategy would be more suitable. But here is the problem — our LazyStudent in current implementation doesn’t allow this kind of flexibility. LazyStudent class is coupled with a search service that it is using. Let’s decouple it!

Now LazyStudent is still dependent on search service, but not on a specific one, any interface implementation will be sufficient. Good, but when do we decide which search implementation is going to be used, or in other words when is this dependency provided then?

We have now the lazyStudent object, which is initialized with the help of pre-created search strategies. Basically, dependency injection has just happened. We’ve made the LazyStudent class independent from any specific implementation — it is not anymore created inside this class, instead it is injected via the class constructor. Responsibility for search type moved one level up.

You may have noticed that one aspect of LazyStudent class can be improved in the same way we’ve done it just now. Property nameOfTheTextBook restricts students in the way that they will forever search in the same textbook. Sounds like a good reason for a change.

Ok, now all the control is in the ExamPreparation class, we’ve just moved all the decisions one level up. We’ve seen an example of dependency injection and most probably you have now the only question in mind — what is then so special about this concept? Let’s look at the theory of this pattern to understand it better.

A bit theory, finally

Usually, to follow a dependency injection principle there should be several parties involved:

  1. An interface of a service — SearchService
  2. Interface implementation — ExactMatchSearchService or LikeMatchSearchService
  3. A client of the service, this part should not be aware of any service implementations in order to support flexibility— LazyStudent
  4. Injector, container, provider and many more names, this part is responsible for objects creation and dependency injections — in our last code snippet ExamPreparation

The current implementation of ExamPreparation doesn’t really serve the purpose of a container as it is just an entry point of application plus it is still coupled with LazyStudent and SearchService. Too bad. However, there is an Inversion of control (IoC) to the rescue — the principle, that allows having all this assembling logic in a configurable container (IoC container). There are several frameworks providing this functionality: Spring and Guice are among the most popular right now. With their help, we can set up the container configuration and decouple our ExamPreparation class.

Dependency Injection achieves Decoupling using Inversion of Control.

Clever conclusion from StackOverflow

If you didn’t get the last part completely, don’t worry —first half of this quote above should be clear from the previous example, the second part I am going to demonstrate on the example using Spring framework.

Inversion of Control and Spring

We will use the same code from example before, you can find all the code with Spring configuration here.

What frameworks like Spring do in terms of inversion of control is basically the work in the role of a container — some configuration should be created first then framework does all the job for you — it initializes objects injecting their dependencies and manages their lifecycle. Let’s create this configuration.

Looks similar to the content of ExamPreparation class, here we have declared two beans — implementations of search service, one bean representing the name of the textbook and last but not the least — representative of LazyStudent. The last one is getting his dependencies. Remember, it all is like a sketch of the application’s dependencies, when the application starts it will populate a container with all the objects by the sketch, at first independent objects will be created, they will be injected into dependent ones. To show how to trigger this behavior let’s rewrite ExamPreparation class.

Here we initialized container by specifying the path to this configuration sketch file. It is possible to get any of the objects declared there, for example, the object of LazyStudent which is already initialized with everything that it needed — specific textbook and search strategy. If we want to change any of these it would be sufficient to make the change in configuration and restart the application, no recompilation required.

Conclusion

At first, we’ve seen the example of coupled code — code, where the client code does not only depend on the service and some specific property value but it also has the responsibility to create them. By decoupling this code we’ve seen how easy and straightforward dependency injection is. In order to make this principle more useful and remove coupling from all levels of our code, there is an inversion of control principle with frameworks that support it. We’ve seen it in action with the help of Spring framework, which serves as IoC container.

Main advantages of using DI and IoC:

  1. Code becomes much more readable
  2. Writing unit tests becomes a blessing as all dependencies can be easily mocked
  3. Configuration is very helpful as it reduces boilerplate code and serves as one point of contact in case of dependencies’ changes.

Hope it helped you!

--

--