This is the first part of a series of articles on Dependency Injection. It will give you an idea about what is Dependency Inversion Principle. There are four other parts of the article which explain how to implement DIP.
Introduction
While working on a WPF application, I came across such kind of terms like Unity Container, IoC, Dependency Injection. At that time, I was confused, thinking of the need of all these. But later on, when I gradually knew about the benefits, I realized the actual need of it.
In this article, I will try to explain the need and usage of DI and IoC. Basically, this article is divided into five parts:
This part of the article is about Dependency Inversion Principle. Hope you will find this article easy to understand and implement.
Prerequisites
Better to have little knowledge on the following items:
- Open/closed principle
- Interface Segregation principle
Dependency Inversion Principle (DIP)
DIP is one of the SOLID principles, which was proposed by Sir Robert Martin C. in the year of 1992.
According to C. Robert Martin's Dependency Inversion Principle:
- High-level modules should not depend on low-level modules. Both should depend on abstractions.
- Abstractions should not depend upon details. Details should depend upon abstractions.
DIP refers to inverting the conventional dependency from high-level-module to low-level-module.
Example from Bob Martin Peper journal:
In Figure 1.a copy program (high-level module) which reads from keyboard and writes to printer. Here the copy program depends on Read Keyboard and Write Printer and tightly coupled.
public class Copy
{
public void DoWork()
{
ReadKeyboard reader = new ReadKeyboard();
WritePrinter writer = new WritePrinter();
string data = reader.ReadFromKeyboard();
writer.WriteToPrinter(data);
}
}
This implementation seems perfectly fine until we have a requirement of adding more reader or writer to the program. In that case, we need to change the copy program to accommodate new readers and writers and need to write a conditional statement which will choose reader and writer based on the use, which violates Open/Close principle of object oriented design.
For example, we want to extend copy program (see figure 1.b) which also can read from scanner and write to flash disk. In such case, we need to modify our copy program:
public class Copy
{
public void DoWork()
{
string data;
switch (readerType)
{
case "keyboard":
ReadKeyboard reader = new ReadKeyboard();
data = reader.ReadFromKeyboard();
break;
case "scanner":
ReadScanner reader2 = new ReadScanner();
data = reader2.ReadFromScanner();
break;
}
switch (writerType)
{
case "printer":
WritePrinter writer = new WritePrinter();
writer.WriteToPrinter(data);
break;
case "flashdisk":
WriteFlashDisk writer2 = new WriteFlashDisk();
writer2.WriteToFlashDisk(data);
break;
}
}
}
Similarly, if you keep on adding more reader or writer, we need to change the implementation of copy program as copy program is depending on implementation of reader and writer.
To resolve this problem, we can modify our copy
program to depend on abstractions rather depending on the implementation. The following figure explains about inverting the dependency.
In the above figure, Copy
program depends on two abstractions, IReader
and IWriter
to execute. As long as low level components confirm to the abstractions, copy
program can read from those components.
Example, in the above figure, ReadKeyboard
implements IReader
interface and WritePrinter
implements IWriter
, hence using IReader
and IWriter
interface copy program can perform copy operation. So if we need to add more low level components such as scanner and flash disk, we can do so by implementing from scanner and flash disk. The following code illustrates the scenario:
public interface IReader
{
string Read();
}
public interface IWriter
{
void Write(string data);
}
public class ReadKeyboard : IReader
{
public string Read()
{
}
}
public class ReadScanner : IReader
{
public string Read()
{
}
}
public class WritePrinter : IWriter
{
public void Write(string data)
{
}
}
public class WriteFlashDisk : IWriter
{
public void Write(string data)
{
}
}
public class Copy
{
private string _readerType;
private string _writerType;
public Copy(string readerType, string writerType)
{
_readerType = readerType;
_writerType = writerType;
}
public void DoWork()
{
IReader reader;
IWriter writer;
string data;
switch (readerType)
{
case "keyboard":
reader = new ReadKeyboard();
break;
case "scanner":
reader = new ReadScanner();
break;
}
switch (writerType)
{
case "printer":
writer = new WritePrinter();
break;
case "flashdisk":
writer = new WriteFlashDisk();
break;
}
data = reader.Read();
writer.Write(data);
}
}
In this case, details depend on abstractions but still high-level class depends on low-level modules. As we are instantiating low-level module object in the scope of high-level module, high-level module still needs modification on addition of new low-level components, which doesn't fully satisfy DIP.
In order to remove the dependency, we need to create the dependency object (low-level component) outside of high-level module, and there should be some mechanism to pass that dependency object to depending module.
Now a new question arises, how can we implement Dependency Inversion.
One of the answers to the above question can be Inversion of Control (IoC). Consider the following code segment:
public class Copy
{
public void DoWork()
{
IReader reader = serviceLocator.GetReader();
IWriter writer = serviceLocator.GetWriter();
string data = reader.Read();
writer.Write(data);
}
}
The highlighted code replaces the logic to instantiate reader and writer object. Here, we are inverting the control creation from Copy
program (high-level module) to service locator. Hence the copy
program need not be changed with any addition/removal of low-level module.
Dependency Injection is one of the mechanisms to implement IoC. In the next parts of this article, I will be covering what is an Inversion of Control (IoC), and the way to implement the Dependency Inversion Principle using different mechanism (Dependency Injection (DI) is one of the implementations).
Summary
In this part of the article, I have explained Dependency Inversion Principle (DIP), and its need in the real time scenario.
In later part of the article, I will explain Inversion of Control (IoC) and Dependency Injection (DI).
History
- 29th April, 2020, Fixed typos
- 12th March, 2013: First revision