This post is also available in: Español
In object-oriented programming (OOP) you can think of objects or classes as components or modules that work together to provide the program’s functionality. In a well-structured program, you may want to replace Module A with Module B and even add new components to Module B, you may want to add unit testing or refactoring of legacy code. To accomplish this easily, you want to develop components that are loosely decoupled from each other, loosely decoupled means independent from each other.
Dependency Injection is a technique for developing software components that are loosely coupled from their dependencies.
To better understand the concept of DI, I won’t use Dependency Injection containers like: Spring, Google Guice, Dagger for Android, Ninject for .NET, etc., but these frameworks are extremly helpful if working on big projects.
DI is a type of Inversion of Control (IoC) and a pattern used in almost all programming languages
There are three most common forms of Dependency Injection
- Constructor Injection
- Setter Injection
- Interface Injection
Constructor Injection
The dependency is passed as an argument to the constructor
Setter Injection
The dependency is passed as an argument to a method, this is also called property injection or field injection
Interface Injection
Dependent classes implement an interface
Let’s have a look at the following program in Java:
Ferrari.java
1 2 3 4 5 6 7 |
public class Ferrari { public String drive() { return "I drive a Ferrari"; } } |
Driver.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public class Driver { private Ferrari ferrari; Driver() { this.ferrari = new Ferrari(); } public void drive() { System.out.println(ferrari.drive()); } public static void main(String... args) { Driver driver = new Driver(); driver.drive(); } } |
Output: I drive a Ferrari
In this example, the Driver class is tightly coupled to its dependency (Ferrari), if the driver had to drive a Porsche, we would have to refactor the Driver class.
Let’s decouple the class Driver from the class Ferrari in such a way that the class Driver can drive any car he wants.
Constructor Injection
In a first step, we define an interface Car
Car.java
1 2 3 |
public interface Car { public String drive(); } |
Now the class Ferrari implements the interface Car
Ferrari.java
1 2 3 4 5 6 7 |
public class Ferrari implements Car { public String drive() { return "I drive a Ferrari"; } } |
Let us create another class of type Car
Porsche.java
1 2 3 4 5 6 7 |
public class Porsche implements Car { public String drive() { return "I drive a Porsche"; } } |
Now we refactor the Driver class so that an instance of Car is passed as an argument to the constructor, then set the dependency to a local private field and use it everywhere within the class.
Driver.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public class Driver { private Car car; Driver(Car car) { this.car = car; } public void drive() { System.out.println(car.drive()); } public static void main(String... args) { Driver ferrari = new Driver(new Ferrari()); ferrari.drive(); Driver porsche = new Driver(new Porsche()); porsche.drive(); } } |
Output:
I drive a Ferrari
I drive a Porsche
Setter Injection
In this approach we provide a setter method (setCar) that injects the dependency. Basically the same as before, but using a setter method instead of the constructor.
Driver.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class Driver { private Car car; public void setCar(Car car) { this.car = car; } public void drive() { System.out.println(car.drive()); } public static void main(String... args) { Driver driver = new Driver(); driver.setCar(new Ferrari()); driver.drive(); driver.setCar(new Porsche()); driver.drive(); } } |
Output:
I drive a Ferrari
I drive a Porsche
Interface Injection
Each class that uses the dependency must implement an interface, in this case CarSetter, which provides a method for injecting the dependency on which it depends.
Driver.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class Driver implements CarSetter{ private Car car; public void setCar(Car car) { this.car = car; } public void drive() { System.out.println(car.drive()); } public static void main(String... args) { Driver driver = new Driver(); driver.setCar(new Ferrari()); driver.drive(); driver.setCar(new Porsche()); driver.drive(); } } |