Dependency Injection: What? Why? How?

Of the various topics I get asked about almost every live stream Q&A, Dependency Injection (which I may refer to as DI at times), is probably number two in popularity. Most senior developers seem to advocate it, yet many intermediate developers attempt to use tools like Dagger 2, and wind up with animalistic rage at how difficult such tools can be to configure.

To summarize this article in a few points:

  • Dependency Injection is a needlessly intimidating name for a very simple concept in principle
  • You do not need any tools (libraries) to inject dependencies; especially in small projects
  • In large and complex projects, these tools can help you quite a lot
  • If you care at all about writing good quality software, achieving separation of concerns in your architecture, and testing your applications with ease, you should definitely implement it

What Is Dependency Injection?

class Controller {
private UserInterface ui //this is a dependency
private Database model //this is also a dependency
//We call them dependencies, because Controller "depends" on
//them to do what it needs to do
/*As you can see, Controller does not build its own dependencies, and therefore we have implemented "dependency injection" in this class. Seriously, it is not more complicated than this. */
public Controller(UserInterface ui, Database model){
this.ui = ui
this.model = model
} //...
}

The above snippet uses constructor arguments to achieve what is commonly referred to as “constructor injection”. In the next snippet, we will look at the same pseudo class, which does not implement dependency injection:

class Controller {    private UserInterface ui //this is still a dependency
private Database model //this is also still a dependency

//Notice a lack of arguments
public Controller(){
this.ui = new UserInterface()
this.model = new Database()
}//...
}

The above snippet builds its own dependencies. With a few exceptions, that is a bad way to build things. Before I explain why, let me explain the concept again in terms which should hopefully explain things in a clearer way.

Using My Words This Time…

Build Logic (a sub-set of control logic), can be thought of as instructions (function/method calls) which are concerned with building the different parts of a software system.

With few exceptions, an object which uses other objects, should not also build the objects it uses.

With few exceptions, an object which builds objects, should not also use the objects it builds.

This is simple and straight-forward application of separation of concerns. In fact, I actually think of build logic as being a distinct layer of my software architectures; whether I write it myself or use a tool.

Ok, But Why?

  • Separating out build logic reduces the size (literally fewer lines of code) of the classes like Presenters and Controllers
  • If a class builds its own dependencies, then it is more difficult test and change
  • If a class is given its dependencies, we can give it “fake” versions of those dependencies to test it very easily (Note: DI works very well with interfaces, abstract classes, and protocols!!!)
  • If a class does not build its own dependencies, you can share and manage instances of those dependencies across many different objects, thus increasing memory efficiency and avoiding shared mutable state problems

The benefits of separating build logic into distinct parts of a system are especially important to those who wish to test their systems. If you are not clear as to why you should definitely do that (unless you do not actually care about building good software), check out my explanation of testing here.

Ok, But How?

Injector.kt:

class Injector(private var activity: AppCompatActivity) {    private var validator: ValidatorImpl = ValidatorImpl    
private var calculator: CalculatorImpl = CalculatorImpl
fun providePresenter(view: CalculatorFragment): IViewContract.Presenter {
return CalculatorPresenter(
view,
ViewModelProviders.of(activity)
.get(CalculatorViewModel::class.java),
EvaluatorImpl(calculator, validator),
DispatcherProvider
)
}
}

CalculatorPresenter.kt (Note: In Kotlin, the brackets following the name of the class are the Constructor):

class CalculatorPresenter(private var view: IViewContract.View,
private var viewModel: IViewContract.ViewModel,
private val eval: IEvaluator,
private val dispatcher: DispatcherProvider) :
IViewContract.Presenter, Observer<String>, CoroutineScope {
//...
}

So, the truth of the matter is that you can write your own dependency injection implementation without needing any libraries to do it. For beginners, I actually recommend doing this at least once, so that you understand what the libraries are actually doing for you.

What About Dagger 2 And Other Libraries?

With that being said, Dagger 2 can be extraordinarily annoying to work with and learn, even when your frameworks (such as Android) and languages (such as Kotlin) are not bugging it out.

My honest opinion here, is that you will need to experiment with these options by doing; not just reading articles like this. Know it in code.

My advice for getting started with DI would be to:

  1. Start with a hand-written implementation in a simple project.
  2. Take a crack at Dagger 2 (or some other DI library) in a separate branch of that same project. Expect many compilation failures, but Dagger 2 is actually helping you spot errors sooner (you did not even need to deploy your program to note it is not going to work)!

Unfortunately, getting Dagger 2 to work in a complex multi-module project can be almost exponentially more complicated than doing so in a simple project. My brogrammer Davor Maric has a good project demonstrating an advanced set up here. Make sure to have lots of good open source code examples beside you when you try to implement it; the documentation does not paint the clearest picture. Not hating though; I have seen much worse.

Support

Consider donating if you learned something:
https://www.paypal.me/ryanmkay

Written by

Self-taught software developer & student of computer science.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store