Introduction
Hello friends! Today I'm here to write an article about Dependency Inversion principle (DIP) which is one of the tenets of SOLID design principle. SOLID is an acronym in which D stands for Dependency Inversion principle. I will not say that this principle is in anyway more or less important than other four design principles but it is used more explicitly a lot because market has huge number of products which are famously known as IoC containers which can be best utilized by software modules exhibiting Dependency Inversion principle. So before you know IoC you should have good understanding of DIP. That's the reason why you get more questions on DI principle in interviews as well :)
Agenda
I've decided to break my entire agenda into two part series because I wanted to draw the clear crisp line between Dependency inversion principle and Dependency Injection design pattern (which is a way of implementing IoC). I hope this is the better way to help readers appreciate my motto. I hope my long article is not too overwhelming. Here is the agenda for current article:
- What is Dependency Inversion Principle and its plain vanilla implementation
- Understand that Dependency Inversion principle can be implemented in totality without existence of Dependency Injection pattern in your modules or presence of IoC containers in your project.
Second portion of my article will cover following things. It can be found here:
- What is Dependency Injection? Its plain vanilla implementation? Understand that Dependency Inversion Principle is NOT Depenency Injection though they share same initials. It is simply NOT. Vice versa is obviously true as well.
- Understand that even Dependency Injection can be implemented if your modules and components don't follow or exhibit the Dependency Inversion principle.
In the end I want to prove that "Dependency Inversion" principle and "Dependency Injection" pattern are not mirror imge of each other. They work in conjunction. They complement each other. One will feel uncomfortable without other. I will show you how. The amount of code changes for any new requirement will be minimum only when you apply both DIP and Dependency injection together.
Prerequisites
A basic knowledge of C# language and Visual Studio IDE from Microsoft is desired while reading this article and using the attached source code.
A note for attached code
You need following softwares as prerequisite to run the attached code on your computer:
- Visual Studio 2010 or above.
- Microsoft SQL Server. Even an express edition would be enough.
The attached code contains a file "DatabaseScripts.sql" which you will need to run on your local sql server instance to run the code without error. In case you do not have SQL Server on your machine, still you will be able to understand the code. To run the code without SQL Server you will have to comment a few lines of code which does the task of saving records into database present in "SaveDataToStorage" function. "SaveDataToStorage" function is present in multiple files.
Conventions
I will NOT use the abbreviation DI in my article after the following statement. The problem is that the principle "Dependency Inversion" and the pattern "Dependency Injection" share the same initiatials - DI. Possbily this is also a source which is why people confuse so much between these two cocepts. The two concepts gets so intermingled that developers start considering them an alter-ego of each other. We fail to draw a clear line and distinguish them separately. Using abbreviation would have saved some typing efforts for me but then it will simply further your confusion and blurr the line between two concepts when my intent is opposite.
Special Note before we start: If you are completely new to the world of design principles, patterns and practices then you are all set. If you have some fair or basic idea then you might want to put the concepts of Inversion of Control(IoC) off the shelf for a while to understand the gist of the article. This article is NOT about IoC.
Dependency Inversion Principle: Let's begin from scratch
What books say? I've broken two statements into four to take you through an evolution in my article :
-
- High level modules should not depend upon low level modules.
- Both high level modules and low level modules should depend upon abstractions.
-
- Abstractions should not depend upon details.
- Details should depend upon abstractions.
There are four statements in above two points. Let's understand them thoroughly one by one.
Let's understand the terms used in above four statements first:
Module: It is a high level abstraction for a grouping of components which together try to delivery a functionality for e.g. logging module in your application which is responsbile for logging events and log statements into a file or event viewer. A module can be a class or a component or an assembly or even a group of assemblies. In our example we will take a class as our module.
Abstraction: Anything which can't be instantiated or is not concrete. It is simply representation of a contract between two parties for e.g. An abstract class or an interface. Both abstract class and an interface are types of abstraction with interface being a pure abstraction. I will use both terms interchangeably during my article.
Statement # 1.a : "High level modules should not depend upon low level modules".
Let me try to rephrase it a bit for you : "High level modules should not depend upon (concrete implementation of) low level modules". Let's try to understand it what exactly do I mean by the words I've quoted inside braces in italics.
Let's first see how a high level module depends on a low level module and what problems the concrete implementation poses due to which technology people thought against it. Have a look at below code:
I've an employee class which exposes an API "SaveEmployeeDetails" to save employee details. Check the below code:
Code snippet # 1 (EmployeeWithSqlDb.cs file in attached code)
using System.Data.SqlClient;
namespace DependencyInversionPrincipleSqlDbDependency
{
public class Employee
{
private string empName;
private int salary;
public string EmployeeName
{
get { return empName; }
set { empName = value; }
}
public int Salary
{
get { return salary; }
set { salary = value; }
}
private bool IsValidEmployee(string inputName, int inputSalary)
{
return !string.IsNullOrEmpty(intputName) && inputSalary > 0;
}
public void SaveEmployeeDetails()
{
DatabaseStorageAgent persistanceStorageAgent = new DatabaseStorageAgent();
if (IsValidEmployee(empName,salary))
{
string queryText = string.Format("insert into Employee (Name,Salary) values ('{0}',{1}", empName, salary);
var sqlCon = persistanceStorageAgent.GetSqlConnection();
persistanceStorageAgent.ExecuteNonQuery(sqlCon, queryText);
}
}
}
public class DatabaseStorageAgent
{
public SqlConnection GetSqlConnection()
{
return new SqlConnection("Data Source=rasik-PC;Initial Catalog=EmployeeDB;Integrated Security=SSPI;");
}
public void ExecuteNonQuery(SqlConnection sqlCon, string queryText)
{
var sqlCmd = new SqlCommand(queryText, sqlCon);
sqlCmd.ExecuteNonQuery();
}
}
}
Code Explanation: Here "Employee" is the high level module. "DatabaseStorageAgent" is the low level module. The high level module "Employee" is dependent on the low level module "DatabaseStorageAgent" to save employee details to a persistent medium sql server database. So here the dependency is downward from high level module to low level module. Everything looks good here. Then why statement # 1.a suggests against it.
The real problem: As part of software development we always want to minimize code changes whenever we have to alter the functionality of our software due to new upcoming requirements. Let's see this if we are really able to achieve this goal. I've got a new requirement. The customer now wants to save the employee data in MS-Access database instead of SQL server database. This will result in changes in employee class and its dependency "DatabaseStorageAgent" as below :
Code snippet # 2 (EmployeeWithMsAccessDb.cs in attached code)
using System.Data.OleDb;
namespace DependencyInversionPrincipleMsAccessDbDependency
{
public class Employee
{
public void SaveEmployeeDetails()
{
DatabaseStorageAgent persistanceStorageAgent = new DatabaseStorageAgent();
if (IsValidEmployee(empName,salary))
{
string queryText = string.Format("insert into Employee (Name,Salary) values ('{0}',{1}", empName, salary);
var oleDbCon = persistanceStorageAgent.GetOleDbConnection();
persistanceStorageAgent.SaveInfoToAccessFile(oleDbCon, queryText);
}
}
}
public class DatabaseStorageAgent
{
public OleDbConnection GetOleDbConnection()
{
return new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\AccessDatabases\\EmployeeDb\\employee.mdb;Persist Security Info=True");
}
public void SaveInfoToAccessFile(OleDbConnection sqlCon, string queryText)
{
var oleDbCmd = new OleDbCommand(queryText, sqlCon);
oleDbCmd.ExecuteNonQuery();
}
}
}
What all changed in code: The changes are humongous. "DatabaseStorageAgent" class has changed in entirety because now it has to act for an MS Access database. So that is fairly OK. But wait a second something else has also changed. Our client code or you can say the consumer code inside "Employee" class which was depending on "DatabaseStorageAgent" class also had to be modified. Observe the italics code inside SaveEmployeeDetails function inside "Employee" class. Now this is real trouble just because my dependency class is saving the employee details in MS Access database instead of SQL Server database I've to change my "Employee" class code like anything.
This was certainly not our intent. Isn't it? What is The real problem? The key attribute exhibited by "Employee" class which is the actual root cause of this problem is - "High level module is dependent on (concrete implementation of) low level module. The text I've mentioned in paranthesis in my last statement should make you think something. The problem is concrete imlementation. We've tightly bound ourselves to concrete implementaion of "DatabaseStorageAgent".
This problem is also known as the problem of Rigidity. If tomorrow customer expects us to save the employee details in a third type of database let's say oracle we will again go through the whole cycle again resulting is multiple changes at multiple places (site of change as well as the site of consumption). Your software is rigid or difficult to change. Every small change is resulting in cascading effect of changes because you are literally peeking into internal implementation of the dependency class the way it has been used in high level module. You will also employ the whole testing cycle again for "Employee" class as well as it has got impacted when real change took place mostly in the "DatabaseStorageAgent" class. Did you get a clue by now how to fix this problem. Let's evolve to statement # 1.b which help us sort out this problem.
Statement # 1.b : "Both high level modules and low level modules should depend upon abstractions".
Yes. That's the thing. If we can somehow make both the high level module and low level module to depend upon abstractions I think it will get solved. Let's see it in action through code. For abstraction we will use an interface. Let's rewrite the code snippet # 1 again where we started. I've provided a new implementation as shown below:
Code snippet # 3 (EmployeeWithAbstractDependencySqlServer.cs file in attached code)
using System.Data;
using System.Data.SqlClient;
namespace DependencyInversionPrincipleAbstractDbDependencySqlServer
{
public class Employee
{
public void SaveEmployeeDetails()
{
if (IsValidEmployee(empName,salary))
{
IStorageAgent persistanceStorageAgent = new DatabaseStorageAgent();
string queryText = string.Format("insert into Employee (Name,Salary) values ('{0}',{1}", empName, salary);
var connection = persistanceStorageAgent.GetPersistantStorageConnection();
persistanceStorageAgent.SaveDataToStorage(connection, queryText);
}
}
}
public interface IStorageAgent
{
IDbConnection GetPersistantStorageConnection();
void SaveDataToStorage(IDbConnection Connection, string queryText);
}
public class DatabaseStorageAgent : IStorageAgent
{
public IDbConnection GetPersistantStorageConnection()
{
return new SqlConnection("Data Source=rasik-PC;Initial Catalog=EmployeeDB;Integrated Security=SSPI;");
}
public void SaveDataToStorage(IDbConnection dbConnection, string queryText)
{
var sqlCmd = new SqlCommand(queryText, (SqlConnection)dbConnection);
sqlCmd.ExecuteNonQuery();
}
}
}
What all changed in code: You can see that I've introduced an interface "IStorageAgent". Both the high level module and low level module now depend upon this common interface. High level module "Employee" now depends upon interface IStorageAgent in implementation of its function "SaveEmployeeDetails". The low level module "DatabaseStorageAgent" now depends upon interface "IStorageAgent" as it implements it. But the real question is how do we get benefited with the new changes where High level module is now NOT dependent on low level module but both depend upon an abstraction (here IStorageAgent interface represents that abstraction/contract). To enjoy the real benefit let's reimplement the same customer changes when he wanted to switch from SQL Server database to MS Access database again and notice many changes we've to make now. Here is the code in action:
Code snippet # 4 (EmployeeWithAbstractDependencyMsAccess.cs file in attached code)
using System.Data;
using System.Data.OleDb;
namespace DependencyInversionPrincipleAbstractDbDependencyMsAccess
{
public class Employee
{
public void SaveEmployeeDetails()
{
IStorageAgent persistanceStorageAgent = new DatabaseStorageAgent();
if (IsValidEmployee(empName,salary))
{
string queryText = string.Format("insert into Employee (Name,Salary) values ('{0}',{1}", empName, salary);
var oleDbCon = persistanceStorageAgent.GetPersistantStorageConnection();
persistanceStorageAgent.SaveDataToStorage(oleDbCon, queryText);
}
}
}
public interface IStorageAgent
{
IDbConnection GetPersistantStorageConnection();
void SaveDataToStorage(IDbConnection Connection, string queryText);
}
public class DatabaseStorageAgent : IStorageAgent
{
public IDbConnection GetPersistantStorageConnection()
{
return new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\AccessDatabases\\EmployeeDb\\employee.mdb;Persist Security Info=True");
}
public void SaveDataToStorage(IDbConnection dbConnection, string queryText)
{
var oleDbCmd = new OleDbCommand(queryText, (OleDbConnection)dbConnection);
oleDbCmd.ExecuteNonQuery();
}
}
}
The Real Solution: So we have changed the code mentioned in code snippet # 3 to make our low level dependency "DatabaseStorageAgent" support MS Access database. Now compare the number of changes you had to make to the high level module to incoporate these changes. The good news is all the changes have been done in your low level dependency "DatabaseStorageAgent" (shown in italics) and the total number of changes in your high level module "Employee" is ZERO. Does that sound like a hurrah moment. Yes it is. You've removed rigidity from your system. Your dependencies can continue to change their internal functionality without impacting their client code even by a single bit. This all was made possible by statement 1.b of dependency inversion principle.
The story doesn't end here: Few things in this world don't have an end. Customer's changing requirement is one of such things in this world. Now I've got one new requirement. Cutomer now wants to save employee details using a call to a web service. No more interaction with local databases. Of course a web service will generally abstract all the implementation details of how it is saving the employee details to a persistant storage inside a web service call. You generally never have to make multiple calls like getPersistantStorageConnection and SaveDataToStorage over web to get your things done. Let's get on with code to understand it fully. Let's implement this requirement keeping principles 1.a and 1.b in mind, of course we've burned our hands to learn 1.a and 1.b :)
Code Snippet # 5 (EmployeeWithWebServiceStorage.cs file in attached code)
using System.Data;
using System.Data.SqlClient;
namespace DependencyInversionPrincipleWebServiceImplementation
{
public class Employee
{
public void SaveEmployeeDetails()
{
IWebService persistanceStorageAgent = new EmployeeWebService();
if (IsValidEmployee(empName,salary))
{
persistanceStorageAgent.SaveEmployeeData(empName, salary);
}
}
}
public interface IWebService
{
void SaveEmployeeData(string name, int salary);
}
public class EmployeeWebService : IWebService
{
public void SaveEmployeeData(string name, int salary)
{
}
}
}
What all changed in code: We've introduced a new web service "EmployeeWebService" which has the power to make web service call and save employee information. "EmployeeWebService" is the new low level module in place of "DatabaseStorageAgent". "IWebService" is the new abstraction on which both high level module "Employee" and low level module "EmployeeWebService" depends as per principle 1.a and 1.b. So far so good. But Hey wait! Is everything alright here? Let's observe the changes which has happened in code snippet # 5 as compared to code snippet # 3 and 4. When we moved from SQL database (code snippet # 3) to MS Access database (code snippet # 4) no changes had been made in high level dependency "Employee". But what has happened in code snippet # 5. I've marked all the changes in italics. Our dependency code has changed which is perfectly alright as now we've moved to a web service implementaion. But is our client code or the high level module intact? The answer is NO. We had to change the interface being used and the dependency which was getting instantiated. Literally entire implementaion of "SaveEmployeeDetails" function inside "Employee" class had to be changed to incorporate new abstraction (here it is an interface IWebService).
The new real problem: This is bad. Every time the low level dependency is relying on a different interface I've to change my code in high level dependency. What's the clue here? You are right. Let's evolve to Dependency Inversion principle statement # 2.a
Statement # 2.a : Abstractions should not depend upon details.
If we can follow this statement of dependecy inversion principle I think we can solve this issue. Let's understand this. Abstractions we all know by now. It is the common contract between high level module and low level module. What is detail here? Detail is essentially the functionality exibhited by your low level module. For e.g. "DatabaseStorageAgent" module can save employee data to some databse (sql server or MS access), "EmployeeWebService" module can save employee data using a web service. So here the problem in our case is that our abstractions (IStorageAgent Or IWebService) are dependent upon the details or their internal implementation.
If we change the implementation details of low level module from database to a web service we're forced to change our abstractions being used in high level module. As a result changes had to be made to our high level module as well when we moved from database persistant storage to web service call even though our high level module complied with principle 1.a and 1.b. So how to get rid of it?
Yes. You got it right buddy! It is time to evolve to the last statement of this principle. Let's have a look.
Statement # 2.b : Details should depend upon abstractions.
Yes. Exactly! Don't change your abstractions when the implementation details of low level dependencyare changing. Instead force or ask or mandate the details (low level dependency) to comply to a single abstraction which your high level module will dictate. The dependency has got inverted now. Low level modules are now on the mercy of high level module :) I'm sure now you will be able to truly appreciate the inversion, the dependency inversion principle. Now the high level module is NOT dependent upon concrete implementation of low level module. Along with that it will not change its abstration when details (low level module implementation) vary. Let's see now how both low level modules "DatabaseStorageAgent" and "EmployeeWebService" succumb to that enforcement from high level modules. The obvious solution is that they both should implement a common interface decided and dictated by the high level module "Employee". Let's see the code in action:
Code Snippet # 6 (EmployeeWithGenericStorage.cs in attached code):
using System.Data;
using System.Data.SqlClient;
using System.Data.OleDb;
namespace DependencyInversionPrincipleGenericStorageImplementation
{
public class Employee
{
public void SaveEmployeeDetails()
{
IStorageService persistanceStorageAgent = new DatabaseStorageAgentForMsAccess();
if (IsValidEmployee(empName,salary))
{
persistanceStorageAgent.SaveData(empName, salary);
}
}
}
public interface IStorageService
{
void SaveData(string name, int salary);
}
public class EmployeeWebService : IStorageService
{
public void SaveData(string name, int salary)
{
}
}
public class DatabaseStorageAgentForMsAccess : IStorageService
{
public void SaveData(string name, int salary)
{
string queryText = string.Format("insert into Employee (Name,Salary) values ('{0}',{1}", name, salary);
var oleDbCon = GetPersistantStorageConnection();
SaveDataToStorage(oleDbCon, queryText);
}
private IDbConnection GetPersistantStorageConnection()
{
return new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\AccessDatabases\\EmployeeDb\\employee.mdb;Persist Security Info=True");
}
private void SaveDataToStorage(IDbConnection dbConnection, string queryText)
{
var oleDbCmd = new OleDbCommand(queryText, (OleDbConnection)dbConnection);
oleDbCmd.ExecuteNonQuery();
}
}
public class DatabaseStorageAgentForSqlServer : IStorageService
{
public void SaveData(string name, int salary)
{
string queryText = string.Format("insert into Employee (Name,Salary) values ('{0}',{1}", name, salary);
var oleDbCon = GetPersistantStorageConnection();
SaveDataToStorage(oleDbCon, queryText);
}
private IDbConnection GetPersistantStorageConnection()
{
return new SqlConnection("Data Source=rasik-PC;Initial Catalog=EmployeeDB;Integrated Security=SSPI;");
}
private void SaveDataToStorage(IDbConnection dbConnection, string queryText)
{
var sqlCmd = new SqlCommand(queryText, (SqlConnection)dbConnection);
sqlCmd.ExecuteNonQuery();
}
}
}
What all changed in code: We got rid of abstractions "IStorageAgent" as well as "IWebService" which were being driven by low level modules. Our high level module "Employee" is now driving the show. It has now introduced a new interface "IStorageService" and asks all low level modules to implement this common interface without moving their eyelids. Of course now we have put high level module in the driver's seat and low level modules can't questions anything. Either they comply the new interface or get out of business. "EmployeeWebService" now implements the new "IStorageService" interface. We've also introduced two new classes "DatabaseStorageAgentForMsAccess" and "DatabaseStorageAgentForSqlServer" for MS access database and sql server database which will follow same pattern.
So that's it we've made all the chages to our initial code to make it follows all four 1.a, 1.b, 2.a and 2.b statements of our dependency inversion principle.
The core benefits
The low level modules have now become plug and play which essentially have following key impacts:
- Any changes in internal details of low level modules can't affect high level module as they ride on an abstraction dictated by high level module. Every low level module implementing IStorageService interface will have to implement its saveData method. Internally they save the details on disk, database or make a web service call will not bother high level module.
- This allows you to replace lower level parts of your system without major rework. Minimal changes will still be there to plug the new class which you want in place of the older implementation but now the changes are minimum as the dependency is on a contract. If you want to save your data using a web service simply replace "DataStorageAgentForSqlServer" class with "EmployeeWebService" class where instantiation is taking place. Everthing else remains aboslutely same.
Since your high level module gets least impacted now (you simply have to change the dependency being instantiated) so your testing effort for high level module is at minimum once you have fully tested your low level module.
I know this article was a bit lengthy and overwhelming but I hope this has done the trick for you and has taken you to a new level of understanding dependency inversion principle. I've tried explaning you Dependency Inversion principle without talking a bit about Dependency injection, IoC containers or anything like that. I want to make sure that you understand - "Dependency inversion is NOT dependency injection". So it also wraps up my agenda number 2 for this article which we decided at start.
Action item for you
There is one more requirement. Now the customer is interested in saving the employee information on disk in sml files instead of database or using a web service. I asked my fellow developer to write a new dependency class for that. Code for that class is present in "EmployeeWithFileStorage.cs" file in attached code. My fellow developer didn't know anything about Dependency Inversion principle so I told him that another friend of mine knows about it and he will review your code w.r.t. this great SOLID principle to make the implementation of your software more solid. Coincidently that friend is you. Download the code and check out the file I've mentioned above. Can you fix it so that it complies with "Dependency Inversion" principle for the said module. I know you can do it now if you have come this far with me. All the best!
Something is still bothering you: Stay tuned
Though we were able to invert the dependencies to bring the changes to minimum but I know one thing is still bothering your head. The instantiation of the low level dependencies are still happening in the employee class which you might perceive as a hard depdenency. I agree with you for the matter of fact that though we have made the low level dependencies plug and play but still I've to make changes in my class every time I've to plug the new low level dependency. This is something we can say is 50% plug and play. I literally want ZERO changes in my high level module whenever I've to plug a new low level module. So who will complete the formalities for remaining 50%? This remaining 50% is our motivation and agenda of our next article : "Inversion of Control - Now you will know it better than ever". You will soon realize why Dependency Inversion and Dependency Injection complement each other. Stay tuned for the article. I will activate the link for the subsequent article once it is live!
Update: Next part of this article is now available at following URL:
Dependency Injection : How exactly inversion of control takes place
History
Incorporated feedbacks from readers to make the article more robust and crisp so that it conveys the agenda of the article more emphatically.
08-Feb-2017 : Added link to the subsequent part of this article