Dependency Injection the Easy Way
This article shows you how to do dependency injection (DI) in .NET or Java applications by using a simple process. Although DI has been around for a while, the explanations as to how DI works vary considerably. So DI can be a somewhat difficult concept to grasp and even more confusing to apply to new or existing applications.
The DI concept first came on the developer scene in 2000 with Robert Martin's paper on good OO programming design concepts, "Design Principles and Design Patterns" (later to become known as the SOLID principles of good OO programming). The D in SOLID refers to Dependency of Inversion (DOI), which was later more eloquently named Dependency Injection.
The original and still most-often-used explanation explained DOI as reversing how dependencies are managed by a base class. Martin's original paper used the code below as an example of how the Copy class depends on the WritePrinter lower-level class:
void Copy() { int c; while ((c = ReadKeyboard()) != EOF) WritePrinter(c); }
The first obvious problem is that if the parameter list or types are changed for WritePrinter, the updates have to be applied everywhere the method is a dependency. This process increases maintenance and creates more potential for introducing new bugs.
Another problem is that the Copy class can no longer be a candidate for reuse. For example, what if you want to write the keyboard characters to a file instead of a printer? You can modify the Copy class to look like this to achieve this goal (C++ syntax):
void Copy(outputDevice dev) { int c; while ((c = ReadKeyboard()) != EOF) if (dev == printer) WritePrinter(c); else WriteDisk(c); }
Although a new dependency was introduced, WriteDisk, we were no better off (and probably worse) because another principle was violated: the "open for extension but closed for modification" principle.
Martin explains that adding these new if/else conditions make the code more fragile and rigid. The solution is to reverse the dependencies so that the methods doing the writing or reading depend on the Copy class. Instead of "pulling" the dependencies, they are pushed through the constructor.
The refactored code appears below:
class Reader { public: virtual int Read() = 0; }; class Writer { public: virtual void Write(char) = 0; }; void Copy(Reader& r, Writer& w) { int c; while((c=r.Read()) != EOF) w.Write(c); }
Now, the Copy class can easily be reused with different implementations of the Reader and Writer class methods. The Copy class does not know the details of the Reader and Writer types, thus making them reusable with different implementations.
In case all this still does not make sense, I will show you some examples in C# and Java that might help.
Java and C# Example
To illustrate how easy it is to do DI without a DI container, let's start with a simple example that can be refactored into using DI with just a few steps.
Suppose that there is a class called HtmlUserPresentation that constructs an HTML user interface for a user when the methods of this class are called.
A simple example appears below:
HtmlUserPresentation htmlUserPresentation = new HtmlUserPresentation(); String table = htmlUserPresentation.createTable(rowTableVals, "Login Error Status");
Any class using this code in the project now has a dependency on the HtmlUserPresentation class, so the same usability and maintenance problems exist.
One improvement we can make right away is to create an interface with all the signatures of the methods that the HtmlUserPresentation type currently has.
An example of this interface follows:
public interface IHtmlUserPresentation { String createTable(ArrayList rowVals, String caption); String createTableRow(String tableCol); // the rest of the signatures here }
After creating the interface, we change the HtmlUserPresentation class to use the interface. Going back to the instantiation of the HtmlUserPresentation type, we can now use an interface type instead of a base type:
IHtmlUserPresentation htmlUserPresentation = new HtmlUserPresentation(); String table = htmlUserPresentation.createTable(rowTableVals, "Login Error Status");
Creating the interface enables us to easily apply a different implementation for the IHtmlUserPresentation type. For example, if we want to test this type, we can easily substitute the HtmlUserPresentation base type with a different type called HtmlUserPresentationTest.
The changes so far have made the code easier to test, maintain, and scale, but have not done much for reusability because each class using the HtmlUserPresentation type is still aware that this type exists.
To remove the direct dependency, we can pass the IHtmlUserPresentation interface type to the constructor (or method parameter list) of the class or method that will be using it:
public UploadFile(IHtmlUserPresentation htmlUserPresentation)
The UploadFile constructor now has access to all the functionality that the IHtmlUserPresentation type provides, but does not know the details of the class that implements this interface.
In this context, the type is injected when the UploadFile class is instantiated. The IHtmlUserPresentation interface type has now become reusable, thus passing in different implementations to different classes or methods that need different functionality.