Dependency Injection - A Core Concept
If weâve ever chatted about software design, youâve likely heard me rave about Dependency Injection (DI). For those who havenât, I am here to share the lore.
Dependency Injection in its raw form is a design pattern that focuses on delivering a class or function its dependencies from the outside, instead of letting it create them on its own.
So, what exactly is a dependency
? It is something a software program needs to do its job. We can see this in two primary contexts:
Object Dependencies:
In object-oriented programming, a class often relies on other classes to function. These are its object dependencies. The goal of DI is to provide these dependencies to the object when it is created/instantiated.
For example, in a Java program, an Adder
class might need two numbers to perform its calculation. Instead of creating those numbers internally, we can design the class to receive them through its constructor.
public class Adder {
// These are the class dependencies (member variables)
private int number1;
private int number2;
// The constructor is used to "inject" the dependencies
public Adder(int number1, int number2) {
this.number1 = number1;
this.number2 = number2;
}
}
In this case, number1
and number2
are dependencies of the Adder
object because the object cannot be instantiated without them being âinjectedâ through its constructor.
Function Dependencies:
In functional programming, a functionâs dependencies are its parameters. The function is designed to be pure, meaning its output is determined solely by the inputs it receives.
For example, in Kotlin, the addTwoNumbers
function depends on number1
and number2
to perform its calculation.
fun addTwoNumbers(number1: Int, number2: Int): Int {
return number1 + number2
}
The function cannot be executed without these two numbers being passed in as a function call.
The patterns used to deliver a programâs dependencies, as shown above, largely affect its modularity and composability. Iâve come to learn that well-designed systems with dependency injection are made up of loosely coupled parts that are easy to test and maintain.
This feels like a good place to pause. Weâve explored dependencies in a software programâs context. While writing this, I gained even more clarity on the different ways dependencies are delivered, the single-threaded and single-process nature of DI patterns, and even runtime vs. compile-time considerations for DI frameworks.
I might explore these topics in a later post. I should. :)