How to Refactor to Configurable Dependency in 5 Steps

Configurable Dependency, also known as Dependency Injection, is a pattern that enables you to switch dependencies of your application. The term was coined by Alistair Cockburn.

Say your application has a GUI. But your administrator wants to use certain functions via the console. Or your production code calls an external service. But your tests shouldn’t call the service, since it doesn’t provide reliable results. Or the service isn’t always available.

Here’s where a Configurable Dependency is helpful. Depending on context, you use one dependency or the other.

A lot of articles attempt to explain the pattern. But they embed it in a broader context, like ports and adapters architecture. That makes understanding harder than necessary. I know, I’ve written such articles myself.

On top of that, many articles focus on greenfield applications. But most of us have to maintain applications that already exist.

Let’s start with a simple class that has a hard-wired dependency. Then we’ll refactor it to a class that has a Configurable Dependency.

The example is trivial, but the refactoring steps are generally applicable to your own application if you’re in a similar situation.

I’ll refer to an example GitHub project in the article. It shows the steps to perform, in its commit history. At the end of the article, there are appendices for IntelliJ IDEA and Eclipse. They show how to do the refactoring steps in your IDE.

Say you have a class called Calculator. It’s the equivalent of “business logic”. At the end of each calculation, it prints the result to the screen. In a real world application, it might save something to a database instead.

Here’s the code:

package example; public class Calculator { public Calculator(){ } public long add(long one, long two) { long result = one + two; printResult(result); return result; } public long sub(long one, long two) { long result = one - two; printResult(result); return result; } private void printResult(long result) { System.out.println("The result is: " + result); }
}

Now if you want to test that class, your test code may look like this:

package example; import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.*; class CalculatorTest { private Calculator calculator; @BeforeEach void setup() { calculator = new Calculator(); } @Test void addsToNumbers() { long result = calculator.add(1, 2); assertEquals(3, result); } @Test void subtractsToNumbers() { long result = calculator.sub(5, 1); assertEquals(4, result); }
} 

Every time you run your test, it prints the results to the screen. That’s unnecessary. It slows your tests down.

With dependency injection, you can still assert the result in the test. But you avoid printing the result to the screen (or saving the result to the database, the file system, or somewhere else).

Step 1: Move Dependency Creation to the Constructor

Find out where an instance of the dependent class is created. Move creation to the constructor by assigning the instance to a field. Use the field throughout the class, so that only the constructor creates the dependency.

The example code is a bit different. The dependency is a static one, System.out. But as described above, the refactored code assigns it to the printer field in the constructor.

So the Calculator class now looks like this:

package example; import java.io.PrintStream; public class Calculator { private final PrintStream printer; public Calculator(){ printer = System.out; } public long add(long one, long two) { long result = one + two; printResult(result); return result; } public long sub(long one, long two) { long result = one - two; printResult(result); return result; } private void printResult(long result) { printer.println("The result is: " + result); }
}

Run the test. It still needs to pass.

Step 2: Pass the Dependency as a Constructor Argument

Pass the instance as a constructor argument, instead of creating it in the constructor. Here’s the refactored example code:

package example; import java.io.PrintStream; public class Calculator { private final PrintStream printer; public Calculator(PrintStream printer){ this.printer = printer; } public long add(long one, long two) { long result = one + two; printResult(result); return result; } public long sub(long one, long two) { long result = one - two; printResult(result); return result; } private void printResult(long result) { printer.println("The result is: " + result); }
}

For this class to work, you need to adapt each line of code that creates a Calculator object to pass in the dependency. So the line to create the Calculator in CalculatorTest now looks like this:

calculator = new Calculator(System.out);

Run the test. It still needs to pass.

Step 3: Create an Interface and Implementation

Create an interface with the exact same name as the class name of the dependency. Place it in the same package. Put the method in it that the business logic calls.

package example; public interface PrintStream { void println(String text);
}

In the Calculator class, remove the import statement of the dependency, java.io.PrintStream, to use the new interface instead.

Here’s one implementation of the interface. ConsolePrinter contains the original functionality that prints to a screen.

package example; public class ConsolePrinter implements PrintStream{ @Override public void println(String text) { System.out.println(text); }
}

In the test class, pass in the ConsolePrinter. Run the test. It still needs to pass, and print the results to the screen:

package example; import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.*; class CalculatorTest { private Calculator calculator; @BeforeEach void setup() { calculator = new Calculator(new ConsolePrinter()); } @Test void addsToNumbers() { long result = calculator.add(1, 2); assertEquals(3, result); } @Test void subtractsToNumbers() { long result = calculator.sub(5, 1); assertEquals(4, result); }
} 

Step 4: Rename and Clean Up

This is an optional step. You can decide to rename the interface, to give it a more meaningful name. For example, rename PrintStream to Printer.

You can also decide to move the class to a different package and rename its method(s).

Run the test. It still needs to pass.

Step 5: Configure the Dependency

Create another implementation that ignores the text argument, for testing purposes:

package example; public class IdlePrinter implements Printer{ @Override public void println(String text) { // This is empty, because we don't want to print in tests. }
}

Now, you can change a single line of the CalculatorTest to turn off printing:

calculator = new Calculator(new IdlePrinter());

Since Java 8, you don’t even need IdlePrinter. Pass in a Lambda function instead:

calculator = new Calculator(text -> {});

Congratulations!

You have refactored to a Configurable Dependency.

Have a look at the GitHub project commit history to see the changes in code that I performed.

If you have any questions, leave a comment or contact me.

Twitter: https://twitter.com/BertilMuth

LinkedIn: https://www.linkedin.com/in/bertilmuth/

The following appendices explain the concrete refactoring steps in IntelliJ IDEA and Eclipse.

IntelliJ Step 1: Move Dependency Creation to the Constructor

Open the Calculator class. Locate System.out. Right click, select Refactor > Introduce Field. Select initialize in: constructor. Name the field printer. Hit Return.

IntelliJ Step 2: Pass the Dependency as a Constructor Argument

Set the cursor into the constructor, Calculator(). Mark the access to the dependency, System.out. Right click, select Refactor > Introduce Parameter. Name the field printer. Hit Return.

IntelliJ Step 3: Create an interface and Implementation

Create the interface and implementation in the same package:

package example; public interface PrintStream { void println(String text);
}
package example; public class ConsolePrinter implements PrintStream{ @Override public void println(String text) { System.out.println(text); }
}

Go to the business logic class Calculator and remove the import statement import java.io.PrintStream;. Save the file.

In the test class CalculatorTest, use the new implementation class.

Change this:

calculator = new Calculator(System.out);

to this:

calculator = new Calculator(new ConsolePrinter());

Save the file. Run the test and check if it still passes.

IntelliJ Step 4: Rename and Clean Up

Go to the interface PrintStream. Right click on PrintStream and select Refactor > Rename. Enter Printer and press Return. Run the test and check if it still passes.

IntelliJ Step 5: Configure the Dependency

Create another implementation that ignores the text argument, for testing purposes:

package example; public class IdlePrinter implements Printer{ @Override public void println(String text) { // This is empty, because we don't want to print in tests. }
}

Go to CalculatorTest and change this:

calculator = new Calculator(new ConsolePrinter());

to this:

calculator = new Calculator(new IdlePrinter());

Save the file. Run the test and check if it still passes.

The text output should now be off. Well done.

Eclipse Step 1: Move Dependency Creation to the Constructor

Open the Calculator class. Locate System.out. Right click, select Refactor > Extract Local Variable. Click OK.

Mark the newly created local variable out. Right click, select Refactor > Convert Local Variable to Field. Name the variable printer. Choose Initialize in Class Constructors. Check Declare field as ‘final’. Hit OK.

grafik-13

Run the test and check if it still passes.

Eclipse Step 2: Pass the Dependency as a Constructor Argument

Set the cursor into the constructor, Calculator(). Mark the access to the dependency, System.out, and copy it to the clipboard (CTRL-C).

Right click, select Refactor > Change Method Signature. Click Add. Type PrintStream under Type, printer under Name, and paste System.out under Default value. Click OK and Continue.

grafik-14

Change the constructor to look like this:

public Calculator(PrintStream printer){
this.printer = printer;
}

Save the file. Run the test and check if it still passes.

Eclipse Step 3: Create an Interface and Implementation

Create the interface and implementation in the same package:

package example; public interface PrintStream { void println(String text);
}
package example; public class ConsolePrinter implements PrintStream{ @Override public void println(String text) { System.out.println(text); }
}

Go to the business logic class Calculator and remove the import statement import java.io.PrintStream;. Save the file.

In the test class CalculatorTest, use the new implementation class.

Change this:

calculator = new Calculator(System.out);

to this:

calculator = new Calculator(new ConsolePrinter());

Save the file. Run the test and check if it still passes.

Eclipse Step 4: Rename and Clean Up

Go to the interface PrintStream. Right click on PrintStream and select Refactor > Rename. Enter Printer and press Return. Run the test and check if it still passes.

Eclipse Step 5: Configure the Dependency

Create another implementation that ignores the text argument, for testing purposes:

package example; public class IdlePrinter implements Printer{ @Override public void println(String text) { // This is empty, because we don't want to print in tests. }
}

Go to CalculatorTest and change this:

calculator = new Calculator(new ConsolePrinter());

to this:

calculator = new Calculator(new IdlePrinter());

Save the file. Run the test and check if it still passes.

The text output should now be off. Well done.

Posted by Contributor