Why to use Dependency Injection for handling resources during object creation in Java

Introduction

In my earlier blog posts, I have discussed In detail a couple of ways to create objects in Java (apart from the normal way of creating objects with public constructor) – using Static Factory Method and using Builder design pattern. You now already know that both of these approaches have their advantages in certain situations. There is another way of creating objects in places where we want to instantiate a unique stateless object only once. This can be done through the use of the Singleton concept.

Singleton

There are 3 ways by which we can create a Singleton class and instantiate its object. The first way is by instantiating the object through a public static final variable.

package com.singleton;
public class Industry {
    public static final Industry INSTANCE = new Industry();
    private Industry() {
    }
}

From the main method:

System.out.println(Industry.INSTANCE);

The object instance can be called by using Industry.INSTANCE and since the member type is final and constructor is private, no other object can be instantiated. 

The second way of creating a Singleton object is by using a “public static factory method” where the object is created and assigned to a private static final variable. Since the variable type is private, the object cannot be accessed by calling the variable and can only be accessed by calling a public static factory method which returns the singleton object, like “getInstance()” in the below case:

package com.singleton;
public class Industry {
    private static final Industry INSTANCE = new Industry();
    private Industry() {
    }
    public static Industry getInstance() {
        return INSTANCE;
    }
}

From the main method:

System.out.println(Industry.getInstance());

The third way is by creating a single-element enum type of the Singleton

package com.singleton;
public enum Industry {
    INSTANCE;
    String city;
    public String getCity() {
        return this.city;
    }

    public void setCity(String city) {
        this.city = city;
    }
}

From the main method:

Industry industry = Industry.INSTANCE;
industry.setCity("Pune");
System.out.println(industry.getCity());

Problem

In our programming journey, many-a-time we work with classes that depend on some other utility class (or classes) under the hood to provide some specific functionality to the calling client/user. These utility classes behave as resources fed to the main class whose behaviour gets impacted due to them. It is quite common to see that the main class is designed as Singleton and the underlying resource object is created from within it. Let us consider the code below:

package com.dependencyinjection;
public interface ExecutionStatus {
    String getStatus();
}

ExecutionStatus” is an interface implemented by two classes – “LocalExecutionStatus” and “RemoteExecutionStatus

package com.dependencyinjection;

public class LocalExecutionStatus implements ExecutionStatus{
    @Override
    public String getStatus() {
        return "PASS";
    }
}
package com.dependencyinjection;
public class RemoteExecutionStatus implements ExecutionStatus {
    @Override
    public String getStatus() {
        return "FAIL";
    }
}

Let’s consider another class “ExecutionReport” which is the main class here. It wants to utilise an ExecutionStatus type (say “LocalExecutionStatus”)

package com.dependencyinjection;
public class ExecutionReport {

    private final ExecutionStatus executionStatus = new LocalExecutionStatus();
    public static final ExecutionReport INSTANCE = new ExecutionReport();
    private ExecutionReport() {
    }
}

Since it is implemented as a “Singleton” (as you can see above), it is hardwiring the “LocalExecutionStatus” resource to itself. This not only leads to a highly untestable code but also the classes “ExecutionReport” and “LocalExecutionStatus” have become highly coupled. Also, what If the “ExecutionReport” class needs to use the “RemoteExecutionStatus” in future to satisfy the requirement of a certain client/user? One solution to this problem can be if we remove the “final” keyword from the “executionStatus” field and change it according to the requirement, from inside a method. But this approach may lead to many problems during concurrency and multithreading.

Solution with Dependency Injection

A good solution here can be to instantiate different forms of the object of the “ExecutionReport” class depending on the type of execution status requirement passed by the client as a resource to the constructor of the “ExecutionReport” class. This approach is commonly referred to as “Dependency Injection”. Here the ExecutionStatus type can be injected from the client/user as a “dependency” resource to the ExecutionReport class during its object creation process. Below is the modified ExecutionReport code rewritten using Dependency Injection:

package com.dependencyinjection;
public class ExecutionReport {

    private final ExecutionStatus executionStatus;
    public ExecutionReport(ExecutionStatus executionStatus){
        this.executionStatus = executionStatus;
    }
}

From the main method:

public static void main(String[] args) {
    ExecutionStatus executionStatus = new LocalExecutionStatus();
    ExecutionReport executionReport = new ExecutionReport(executionStatus);
    System.out.println(executionReport);
    executionStatus = new RemoteExecutionStatus();
    executionReport = new ExecutionReport(executionStatus);
    System.out.println(executionReport);
}

Conclusion

I have given this example of Dependency injection with a single ExecutionStatus resource. The same concept can be reliably utilised for numerous driver resources parameterised to the ExecutionReport class. Though this example is with a public constructor, the same approach can be safely extended towards object creation through static factory method and builder pattern too. This approach ensures high cohesion, low coupling, reusability, flexibility, maintainability, and testability of the programs.