Dependency Injection 101

istock_000011194935medium

One of the most important design patterns in modern programming is known as Dependency Injection (DI), also know as the Inversion of Control (IoC).  DI is a powerful, yet often misunderstood, tool for developing stable, large-scale enterprise software systems.  We’ll cover the basics here, in preparation for using the Spring framework.

Dependency Injection is a fairly simply concept to understant, but more difficult to appreciate.  The best way to describe what DI is and how it helps is to start with a simple application and work towards the DI model over a series of iterations.

Hello World

We begin were every good tutorial begins, with “Hello, World”.  Figure 1 shows this well-known application as you probably learned it in your first day of any (Java) programming class.

/**
 * Figure 1:  HelloWorld.java
 * 
 * Simple application.
 */

package net.proloquor.learning.hello1;

public class HelloWorld {
   public static void main(String[] args) {
      System.out.println("Hello, World.");
   }
}

From the Command Line

Our HelloWorld application seems perfectly reasonable.  However, it’s not particularly extensible or reusable.  Consider if we wanted to change the output message.  The code would have to be modified for each new message.  To address this, the application could accept the message as a command line argument, as shown in figure 2.

/**
 * Figure 2: HelloWorld.java
 *
 * Simple application with a command line argument.
 */
package net.proloquor.learning.hello2;

public class HelloWorld {

   public static void main(String[] args) {
      if(args.length > 0) {
         System.out.println(args[0]);
      } else {
         System.out.println("Hello, World.");
      }
   }
}

This is better, but what if we then wanted to change how the output was presented?  Instead of sending it to Standard Out, for example, we could perhaps send it to a file, log, webpage, etc.  There are probably other aspects of the application we would like to control without modifying its code, and we’d prefer not to hardcode all these features into a single class.

Basic Dependency Injection

To achieve the flexibility and extensibility we desire, we need to create separate classes that manage specific features independently.  In our example, that means we need a class that provides the message, and a class that renders the message.  For example, a MessageProvider could take the form shown in figure 3.

/**
 *  Figure 3: MessageProvider.java
 *
 *  An interface describing a class that provides messages.
 */
package net.proloquor.learning.hello3;

public interface MessageProvider {
   public String getMessage();
}

So if we need a MessageProvider that provides our “Hello, World” message, it might look like that in figure 4.

/**
 * Figure 4: HelloWorldMessageProvider.java
 * 
 * Provides the "Hello, World" message.
 */
package net.proloquor.learning.hello3;

public class HelloWorldMessageProvider implements MessageProvider {

   @Override
   public String getMessage() {
      return "Hello, World.";
   }
}

Similarly, a MessageRenderer might look like that shown in figure 5.

/**
 *  Figure 5: MessageRenderer.java
 *
 *  An interface describing a class that provides messages.
 */
package net.proloquor.learning.hello3;

public interface MessageRenderer {
   public void render();
   public void setMessageProvider(MessageProvider provider);
   public MessageProvider getMessageProvider();
}

A MessageRenderer that writes the message to Standard Out based on that interface would look like that shown in figure 6.

/**
 *  Figure 6: StanardOutMessageRenderer.java
 *
 *  Renders a message to Standard Out.
 */
package net.proloquor.learning.hello3;

public class StandardOutMessageRenderer implements MessageRenderer {

   private MessageProvider messageProvider = null;
 
   @Override
   public void render() {
      if(messageProvider == null) {
         throw new RuntimeException();
      }
   System.out.println(messageProvider.getMessage());
 }

   @Override
   public void setMessageProvider(MessageProvider provider) {
      this.messageProvider = provider;
   }

   @Override
   public MessageProvider getMessageProvider() {
      return this.messageProvider;
   }
}

Putting it altogether, our application creates instances of our provider and renderer class, configures them as required, and then executes the desired operation.

/**
 *  Figure 7: HelloWorld.java
 * 
 *  Application using separate provider and renderer classes.
 */
package net.proloquor.learning.hello3;

public class HelloWorld {

   public static void main(String[] args) {
      MessageRenderer renderer = new StandardOutMessageRenderer();
      MessageProvider provider = new HelloWorldMessageProvider();
 
      renderer.setMessageProvider(provider);
      renderer.render();
   }
}

Note that, while the MessageRender has a dependency on the MessageProvider, it relies on the main application to create the provider and hand it to, or inject it into, the renderer.  This is the principle feature of DI.

Using Factories

Instead of relying on the application to configure your classes, it’s often better to create Factories to do it for you. For our example, a possible factory is shown in figure 8.

/**
 *   Figure 8: MessageSupportFactory.java
 * 
 *   Factory for HelloWorld application.
 */
package net.proloquor.learning.hello4;

import java.io.FileInputStream;
import java.util.Properties;

import net.proloquor.learning.hello3.MessageProvider;
import net.proloquor.learning.hello3.MessageRenderer;

public class MessageSupportFactory {

   private static MessageSupportFactory instance = null;
   private Properties properties = null;
   private MessageRenderer renderer = null;
   private MessageProvider provider = null;
 
   private MessageSupportFactory() {
      properties = new Properties();
 
      try {
         properties.load(new FileInputStream("hello.properties"));
 
         String rendererClass = 
            properties.getProperty("renderer.class");
         String providerClass = 
            properties.getProperty("provider.class");
 
         renderer = (MessageRenderer)
            Class.forName(rendererClass).newInstance();
         provider = (MessageProvider)
            Class.forName(providerClass).newInstance();
      } catch(Exception e) {
         e.printStackTrace();
      }
   }
 
   static {
      instance = new MessageSupportFactory();
   }
 
   public static MessageSupportFactory getInstance() {
      return instance;
   }
 
   public MessageRenderer getMessageRenderer() {
      return renderer;
   }
 
   public MessageProvider getMessageProvider() {
      return provider;
   }
}

Our MessageSupportFactory maintains instances of our MessageProvider and MessageRenderer, as well as a static instance of itself to make launching it easier.  We could pass to it the name of the specific provider and renderer classes we’d like to use, but these are better stored in a java.utils.Properties file as shown in figure 9.

# Figure 10: hello.properties
#
# Properties for MessageSupportFactory.java

renderer.class=net.proloquor.learning.hello3.StandardOutMessageRenderer
provider.class=net.proloquor.learning.hello3.HelloWorldMessageProvider

Now, our HelloWorld application calls the factory to get instances of the required classes, as shown in figure 10.

/**
 *  Figure 10: HelloWorld.java
 *
 *  HelloWorld application using class factory.
 */
package net.proloquor.learning.hello4;

import net.proloquor.learning.hello3.MessageProvider;
import net.proloquor.learning.hello3.MessageRenderer;

public class HelloWorld {

   public static void main(String[] args) {
      MessageRenderer renderer =    
         MessageSupportFactory.getInstance().getMessageRenderer();
      MessageProvider provider = 
         MessageSupportFactory.getInstance().getMessageProvider();
 
      renderer.setMessageProvider(provider);
      renderer.render();
   }
}

Why DI Matters

In a conventional Java programming model, we would have constructed our MessageRenderer as shown in figure 11.

/** 
 *  Figure 11: Traditional dependency management.
 */
public class StandardOutMessageRenderer {

   private MessageProvider provider = null;

   public MessageRenderer() {
     provider = new HelloWorldMessageProvider();
   }

   public void render() {...}
}

In contrast, our MessageRenderer is structured as shown in figure 12.

/**
 *  Figure 12: Using Dependency Injection.
 */
public class StandardOutMessageRenderer {

   private MessageProvider provider = null;

   public void setMessageProvider(MessageProvider provider) {
      this.provider = provider;
   }

   public void render() {...}
}

What’s the difference?  In the traditional model, the dependent class, StandardOutMessageRenderer in our case, must allocate memory for the HelloWorldMessageProvider class and configure it properly, either through the MessageProvider’s constructor, setter’s, or both.  This is a trival duty in our case, but consider classes that require extensive configuration; their proper management falls on the shoulders of every other class that wants to use it.

In the DI model, the dependent class is handed a fully configured subordinate class, freeing it from the responsibly of managing it.   That duty is left to an external control class, utility, or context, preferably controlled by an external data file listing suitable properties, rather than hardcoded into the class itself.  For complex applications, this is a much more supportable and testable environment.

What’s Next?

While there are volumes of material to fully cover Dependency Injection and similar models, we’ve covered the basics here.  In subsequent posts, we’ll discuss how the Spring framework can be used to implement this model.

Leave a Reply

Your email address will not be published. Required fields are marked *