Dependency Inversion Principle and the Dependency Injection Pattern
Introduction
Every day we write lots of code that is tightly coupled, and as complexity grows that code eventually deteriorates into spaghetti code, which violates the Dependency Inversion Principle. In software design, tight coupling is often considered a liability. When one class knows explicitly about the design and implementation of another class, it raises the risk that changes to one class will break the other class.
On the other hand, loosely coupled code can stay maintainable and well-designed. The benefits of using loose coupling aren’t always instantly evident, but they become visible over time, as the complexity of the code grows. The Dependency Injection pattern is one of the best ways to enable loose coupling. In this article, I will try to explain how we can use DI in our everyday practice with a simple approach without DI containers.
Background
In Uncle Bob’s “SOLID” object-oriented design principles, the “D” stands for Dependency Inversion Principle.
The Dependency Inversion Principle states:
- High-level modules should not depend upon low-level modules. Both should depend upon abstractions.
- Abstractions should not depend upon details. Details should depend upon abstractions.
Sometimes it’s not easy to follow DIP while writing your code. Practice and experience will help. The Dependency Inversion Principle (DIP) helps to loosely couple your code by ensuring that your high-level modules depend on abstractions rather than concrete implementations of lower-level modules. The Dependency Injection pattern is an application of this principle.
The basic idea of this article is to show how we can make our code loosely coupled.
Using the code
Let’s start with the code. Many of us have probably seen (or written) code like this:
public class Email
{
public void SendEmail()
{
// code
}
}
public class Notification
{
private Email _email;
public Notification()
{
_email = new Email();
}
public void PromotionalNotification()
{
_email.SendEmail();
}
}
Here the Notification class has a dependency on the Email class. In this case, the notification creates an instance of the e-mail directly inside the notification’s constructor and knows exactly what kind of email class it’s creating and consuming. This violates DIP.
A class that depends on other classes and knows a lot about them is said to be tightly coupled. When a class knows explicitly about the design and implementation of another class, it raises the risk that changes to one class will break the other class.
What if we want to send some other types of notifications like SMS or save into the DB? To enable this behaviour we have to modify the implementation of the notification class.
To reduce the dependency, we need two steps. First, introduce an abstraction layer between these two classes. We can use an interface or abstract class to represent the abstractions between Notification and Email.
public interface IMessageService
{
void SendMessage();
}
public class Email : IMessageService
{
public void SendMessage()
{
// code
}
}
public class Notification
{
private IMessageService _iMessageService;
public Notification()
{
_iMessageService = new Email();
}
public void PromotionalNotification()
{
_iMessageService.SendMessage();
}
}
Here, we introduce an interface, IMessageService, to represent the abstraction, and ensure that the Notification class only calls methods or properties on that interface.
Second, we need to move the creation of the Email class outside of Notification. We can achieve this with the DI pattern.
DI is the act of supplying all classes that a service needs rather than leaving the responsibility to the service to obtain dependent classes. DI typically comes in three flavours:
- Constructor Injection
- Property Injection
- Method Injection
Using these injections we can achieve the second step. Let’s apply them to our tightly coupled code to make it loosely coupled and follow DIP.
Constructor Injection
This is the most common form of dependency injection. When a class requires an instance of a DEPENDENCY to work, we can supply that DEPENDENCY through the class’s constructor. Now let’s change the Notification class to support constructor injection:
public class Notification
{
private IMessageService _iMessageService;
public Notification(IMessageService _messageService)
{
this._iMessageService = _messageService;
}
public void PromotionalNotification()
{
_iMessageService.SendMessage();
}
}
This code has several benefits: the implementation of the constructor is very simple, it has reduced the number of things Notification needs to know about, any code that wants to create an instance of Notification can look at the constructor and know exactly what kinds of things are necessary to make Notification function. So, our code is now loosely coupled and easily maintainable.
Property Injection
Property injection (or setter injection) is a less common form of dependency injection; it is best used when the dependency is optional. We have to expose a writable property that allows a client to supply a different implementation of the class’s DEPENDENCY than the default. We have to change our Notification class to apply property injection:
public class Notification
{
public IMessageService MessageService
{
get;
set;
}
public void PromotionalNotification()
{
if (MessageService == null)
{
// some error message
}
else
{
MessageService.SendMessage();
}
}
}
We have removed the constructor and replaced it with a property. Now we provide the dependency via a property rather than the constructor. In the PromotionalNotifications method we have to check whether the service dependency has been provided by checking the MessageService value. Again, the code is now loosely coupled.
Method Injection
When a DEPENDENCY can vary with each method call, you can supply it via a method parameter. Suppose our application sends email and SMS, or saves the notification message to the database. We can use method injection and write the code like this:
public class Email : IMessageService
{
public void SendMessage()
{
// code for the mail send
}
}
public class SMS : IMessageService
{
public void SendMessage()
{
// code for the sms send
}
}
public class Notification
{
public void PromotionalNotification(IMessageService _messageService)
{
_messageService.SendMessage();
}
}
Conclusion
Surprisingly, it’s very easy to write tightly coupled code, especially while adding new features. Every time we use the new keyword, we introduce tight coupling. We can minimise this by introducing constructor injection. In general, developers like to use constructor injection over the other two types of injection. Of course, we can mix them: constructor injection for mandatory dependencies, and the other two for optional ones. I hope this article helps you start using DI in your everyday code.