Dependency Inversion Principle (DIP)
We’re at the last of the SOLID principles: the dependency inversion principle (DIP).
Let’s look at Robert C. Martin’s definition of DIP in the book Agile Software Development; it consists of two parts:
High-level modules should not depend on low-level modules. Both should depend on abstractions.
Abstractions should not depend on details. Details should depend on abstractions.
I know this sounds a bit complex, but if you apply the open/closed and Liskov substitution principles to your application, it will already follow the dependency inversion principle.
Remember, the open/closed principle dictates that your application be open for extension and closed for modification. You were able to do this by using interfaces that provided different implementations.
In the Liskov substitution principle, you learned that you should be able to replace implementations with others without breaking your application.
The goal is not to have a tightly-coupled system where every module directly references lower-level modules. That’s why you need to abstract that out to get to a more loosely-coupled system.
Example 1: Tightly Coupled
Let’s create a simple example to show a notification application that’s tightly coupled.
public class Email
{
public string ToAddress { get; set; }
public string Subject { get; set; }
public string Content { get; set; }
public void SendEmail()
{
//Send email
}
}
public class SMS
{
public string PhoneNumber { get; set; }
public string Message { get; set; }
public void SendSMS()
{
//Send sms
}
}
public class Notification
{
private Email _email;
private SMS _sms;
public Notification()
{
_email = new Email();
_sms = new SMS();
}
public void Send()
{
_email.SendEmail();
_sms.SendSMS();
}
}
Take a look at the Notification
class, which is a higher-level class. It has a dependency on the Email
and SMS
classes, which are lower-level classes. It is called a concrete implementation because Notification
is dependent on Email
and SMS
. What you want is an abstraction of this implementation.
Your goal is to make this loosely coupled and reduce dependencies.
How can you quickly look at your application to see how much of it is tightly coupled?
Here’s a helpful trick to remember: look for the new keyword. Usually, the more instances of the new keyword you have, the more tightly coupled your code.
Why?
Remember, you’re instantiating objects, which creates dependencies.
Example 2: Refactor to Follow DIP
Let’s refactor this Notification
example to remove those dependencies.
First, introduce an abstraction that can be relied upon and others can implement.
public interface IMessage
{
void SendMessage();
}
public class Email : IMessage
{
public string ToAddress { get; set; }
public string Subject { get; set; }
public string Content { get; set; }
public void SendMessage()
{
//Send email
}
}
public class SMS : IMessage
{
public string PhoneNumber { get; set; }
public string Message { get; set; }
public void SendMessage()
{
//Send sms
}
}
public class Notification
{
private ICollection<IMessage> _messages;
public Notification(ICollection<IMessage> messages)
{
this._messages = messages;
}
public void Send()
{
foreach(var message in _messages)
{
message.SendMessage();
}
}
}
So, what have we done here?
We abstracted out
SendMessage()
into an interface calledIMessage
.Email
andSMS
implemented theIMessage
interface.The
Notification
depends on the abstraction,IMessage
, instead of a concrete implementation of a class.
By making these changes, you allow Notification
to only care that there is an abstraction that can actually send the messages.
And with that, you have seen the dependency inversion principle.
Let’s Recap!
The dependency inversion principle states the following:
High-level modules should not depend on low-level modules. Both should depend on abstractions.
Abstractions should not depend on details. Details should depend on abstractions.
By following the open/closed and the Liskov substitution principles, your classes will also comply with the dependency inversion principle.
Notice how much you use the new keyword in your application. It can be a sign of too much coupling.
Your goal is to reduce concrete implementation in favor of abstractions.
Now that you’ve got the do’s down, it’s time to cover the don’ts. Learn the STUPID principles and how to avoid them!