Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Dependency Inversion Principle and the Dependency Injection Pattern

4.96/5 (88 votes)
26 Nov 2012CPOL5 min read 177.9K  
How to use DI without DI containers
This article explains how we can use DI in our everyday practice with a simple approach without DI containers.

Introduction

Everyday we write lots of code which are tightly coupled and when complexity grows, code will eventually deteriorate into Spaghetti Code, which violates the Dependency inversion principle. In software design, tight coupling is often considered to be a liability in design. 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 a code grows. Dependency Injection Pattern is nothing but the best way to enable loose coupling. In this article, I will try to explain how we can use DI in our everyday practice with simple approach without DI containers.

Background

Uncle Bob "SOLID" object-oriented design principles, “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 maintain DIP while writing your code. Practice and experience will help you out from this. 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/ implementation of this principle.

The basic idea of this article is how we can make our code loosely coupled.

Using the Code

Let's start from the code. Many of us have probably seen (or written) code like this:

C#
public class Email
{
    public void SendEmail()
    {
        // code
    }
}

public class Notification
{
    private Email _email;
    public Notification()
    {
        _email = new Email();
    }

    public void PromotionalNotification()
    {
        _email.SendEmail();
    }
}

Here, Notification class has a dependency on Email class. In this case, the notification creates an instance of the e-mail directly inside of the notifications constructor and knows exactly what kind of email class it’s creating and consuming. It violates DIP.

A class that has dependency on some other class and knows a lot about the other classes it interacts with 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 behavior, we have to modify the implementation of the notification class.

To reduce dependency, we have to do a couple of steps. Firstly, introduce an abstraction layer between these two classes. We can use interface/ abstract class to represent the abstractions between Notification and Email.

C#
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 have introduced an interface IMessageService to represent the abstraction, and ensure that the Notification class only calls methods or properties on that interface.

Secondly, we need to move the creation of the Email class outside of the Notification. We can achieve this by DI pattern.

DI is the act of supplying all classes that a service needs rather that leaving the responsibility to the service to obtain dependent classes. DI typically comes in three flavors:

  • Constructor Injection
  • Property Injection
  • Method Injection

Using these injections, we can achieve the second step. I am applying these injections in our tightly coupled code. And making our code loosely coupled and maintaining DIP.

Constructor Injection

This is the most common 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:

C#
public class Notification
{
    private IMessageService _iMessageService;

    public Notification(IMessageService _messageService)
    {
        this._iMessageService = _messageService;
    }
    public void PromotionalNotification()
    {
        _iMessageService.SendMessage();
    }
}

In this code, there are 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/ setter injection is a less common dependency injection, it is best used when 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:

C#
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 are providing dependency via properties rather than the constructor. In the PromotionalNotifications method, we have to check that it has already provided the service dependency or not by checking MessageService value. Here, we are able to make our code loosely coupled.

Method Injection

When a DEPENDENCY can vary with each method call, you can supply it via a method parameter. Suppose our application will send Email also SMS or save notification message into DB. We can use method injection. We can write the code in the following way:

C#
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();
    }
}

Here, IMessageService has been implemented in both Email class and SMS class. We can supply different types of MessageService as a parameter to the PromotionalNotification method to perform the notification accordingly. So, here dependency is varying every method call by different parameters.

In a tightly coupled scenario like the example, we can apply any of these injections to make our code loosely coupled. It depends on the scenario which injection we will apply. We can apply more than one in a single scenario.

Conclusion

Surprisingly, it's very easy to write tightly coupled code! One of the many reasons is that while introducing new/ additional features in our code, every time we use a new keyword, we introduce a tight coupling. We can minimize this by introducing constructor injection. In general, developers like to use constructor injection over the other two types of injection. But of course, we can mix both of them, while constructor will be mandatory and other two will be optional. Hope this article will help you to begin DI in your everyday code.

History

  • 20th November, 2012: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)