Refactoring to Configurable Dependency in 5 Steps

Bertil Muth
6 min readAug 18, 2022

--

Photo by Diana Polekhina on Unsplash

Configurable Dependency, a.k.a. 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 adminstrator wants to use certain functions via 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 Dependecy 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 & 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. And refactor it to a class that has a Configurable Dependency. The example is trivial. But the refactoring steps are general. You can apply them in 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.

The Calculator Example

Say you have a class 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:

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

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 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 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:

Run the test. It still needs to pass.

Step 2: Pass dependency as constructor argument

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

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 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.

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.

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

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:

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.

Appendix A — How to refactor in IntelliJ IDEA

IntelliJ Step 1: Move dependency creation to 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 dependency as 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 interface and implementation

Create the interface and implementation in the same package:

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

calculator = new Calculator(System.out);

to

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:

Go to CalculatorTest and change

calculator = new Calculator(new ConsolePrinter());

to

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.

Appendix B— How to refactor in Eclipse

Eclipse Step 1: Move dependency creation to 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.

Run the test and check if it still passes.

Eclipse Step 2: Pass dependency as 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.

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 interface and implementation

Create the interface and implementation in the same package:

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

calculator = new Calculator(System.out);

to

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:

Go to CalculatorTest and change

calculator = new Calculator(new ConsolePrinter());

to

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.

--

--