Dependency Inversion Principle

Hopefully, you’ve been following along and have read or gone over my previous posts on SOLID development principles (You can search the site for the SOLID topic and bring them all up). This is going to be the last in the series which of course is covering the Dependency Inversion Principle. I’ve enjoyed this series, more of an ongoing installment rather than individual posts not necessarily related to one another. I am thinking I’ll probably do more like this in the future, as it helps me to be a bit more consistent in my writing.

Dependency Inversion Principle in Action

The Dependency Inversion Principle (DIP) is really composed of two parts.

  • High-level modules should not depend on low-level modules. Both should depend on abstractions (e.g. interfaces /abstract class).
  • Abstractions should not depend on details (Think interfaces). Details (concrete implementations) should depend on abstractions.

Now we’ve definitely covered the use of interfaces and abstract classes in the previous examples of SOLID principles. I do find it somewhat difficult to use one of these principles in isolation from the others as they do bleed into one another. Just remember that the use of these principles is ultimately going to help us create more robust and flexible code bases. So in this case, DIP is basically saying that our high-level classes don’t need to worry about the low-level details.

Let’s give a code example of how this works in application. We are going to revisit the previously used example of sending messages to our customers. We can will be building on the code we had written and apply this principle including our previous example from the Liskov Substitution Principle (LSP) post. The LSP states “Methods that use references to base classes (or interfaces) have to be able to use methods of the derived classes without knowing about it or knowing the details”. Sounds similar to DIP doesn’t it. Well in many ways it is.

public class Customer
    {
        public int Id;
        public string FirstName;
        public string LastName;
        public string Email;
        public string Phone;
    }
 
    public  interface ILogger
    {
        void Log(string message);
    }
 
    public abstract class Message
    {
        protected Customer Customer { get; }
        protected ILogger Logger { get; }
 
        public Message(Customer customer, ILogger logger)
        {
            Logger = logger;
            Customer = customer;
        }
 
        public abstract void SendMessage();
    }
 
    public class SendTextMessage : Message
    {
        public SendTextMessage(Customer customer, ILogger logger)
            : base(customer, logger)
        {
        }
 
        public override void SendMessage() 
        {
            //Send the message via text...
            //log the message being sent
            Logger.Log($"Sending {Customer.FirstName} a text message");
        }
    }

So far so good, this is using the LSP principle which basically says our implementation classes ought to be based on abstractions. Now, what if we wanted to add a Notification class which will send messages? With respect to the DIP, we would want to have a class that doesn’t care about the details of the messages it is sending, just that it is going to be sending messages of type Message, so anything that inherits from Message will have the SendMessage() method, but our Notification class doesn’t care about the actual implementation of our SendMessage() implementations.

public class Notification
        {
            private IEnumerable<message> messages;
 
            public Notification(IEnumerable</message><message> messages)
            {
                this.messages = messages;
            }
            public void SendMessages()
            {
                foreach (var message in messages)
                {
                    message.SendMessage();
                }
            }
        }
</message>

In essence, we can create another Messenger class which would then implement the necessary details, but our higher level notification class doesn’t care about that, other than the fact that it can message.SendMessage().

In Closing

Remember that these principles are really meant to be a heuristic for clean code, in this example, it really can help to use the Dependency Inversion Principle, however for the sake of making everything be based upon abstraction without forethought can just end up creating unneeded complexity. In my opinion and experience, it’s always best practice to use DIP, but also make sure we aren’t adding unnecessary complexity to our code.

Once again, I really enjoyed writing this series and I hope you have too!

Leave a Reply

Your email address will not be published. Required fields are marked *