• 4 hours
  • Easy

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 3/28/24

“D” for the Dependency Inversion Principle

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:

  1. High-level modules should not depend on low-level modules. Both should depend on abstractions.

  2. 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?

  1. We abstracted out  SendMessage()  into an interface called  IMessage.

  2. Email  and  SMS implemented the  IMessage  interface.

  3. 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!

Example of certificate of achievement
Example of certificate of achievement