Dependency Injection & Inversion of Control

Introduction

Dependency Injection (DI) & Dependency Inversion (or Inversion of Control, IoC) are two complementary software design patterns that are usually implemented together. Although these patterns can be applied to multiple programming paradigms, we will examine them in the context of object-oriented programming and especially C#.

Services

In the context of application architecture, classes that provide functionality (in contrast with classes that are mainly used for data transfer) are usually called services. Software architecture principles dictate that the applications must be implemented as a set of multiple services where each has a single, specific responsibility, and depend on other services for implementing more detailed (lower level) functionality.

For example a UserNotificationService should not contain code for sending the notification (i.e. an email) but it should depend on a EmailService for implementing the details of sending an email. This approach has multiple advantages:

  • It simplifies the implementation of UserNotificationService
  • Allows the EmailService to be used from other services
  • Makes testing both services easier

Dependency Injection

Dependency Injection is the pattern of providing (injecting) the dependencies of a service during it’s construction instead of having the service instantiate these services directly.

Let’s see an example:

public class UserNotificationService
{
    private EmailService _emailService;

    // Constructor with Direct Instanciation
    public UserNotificationService(EmailServiceOptions emailServiceOptions) 
    {
        _emailService = new EmailService(emailServiceOptions)
    }

    // Constructor with Dependency Injection
    public UserNotificationService(EmailService emailService)
    {
        _emailService = emailService
    }
}

Both constructors instantiate the class in the same state, with an initialized _emailService field that can be used, but there are some differences

The DirectInstanciation constructor creates an instance of EmailService and, thus, it needs access to the options of the EmailService. This has the nasty effect of having to provide the EmailServiceOptions to the UserNotificationService which seems a bit counter-intuitive. Also the lifecycle of the EmailService is now tied to the lifecycle of the UserNotificationService which means that we must instantiate an EmailService every time we instantiate a UserNotificationService and we don’t have the option to re-use the same EmailService with from multiple instances of UserNotificationService (which might be an issue in some cases, as for example, if instantiating an EmailService takes considerable time)

The DependencyInjection constructor requires an instantiated EmailService which just stores in the _emailService field. Of course, now the caller of the constructor has to have available an instance of EmailService but we will see how we can handle this issue later.

Dependency Inversion (Inversion of Control)

Dependency Inversion is the pattern of decoupling higher-level services from lower-level services. In the example above the high-level service NotifyUserService depends on the lower-level service EmailService. Although we have injected the service we are still dependent on the specific implementation details of the service for sending the email. If we decide to use another service to notify the user we would need to change the NotifyUserService.

Before discussing how to handle this issue, let’s see an example of using the EmailService

public class UserNotificationService
{
    private EmailService _emailService;

    // Constructor with Dependency Injetion
    public void UserNotificationService(EmailService emailService)
    {
        _emailService = emailService
    }

    public void NotifyUser(User user, string message)
    {
        var email = user.Email;
        _emailService.SendEmail(email, message);
    }
}

Let’s assume now, that the business people decide that we should have two ways of notifying our users, either via email or via an SMS we could of course add a new function and decide at a higher level which one to use.

    public void NotifyUserSMS(User user, string message)
    {
        var phoneNumber = user.PhoneNumber;
        _smsService.SendSMS(phoneNumber, message);
    }

But there is a better way. What if we could force both services (EmailService && SMSService) to have the same method name and parameters? Modern object oriented languages have the concept of interfaces. An interface it’s a contract that is force upon eveyone that wants to implement the interface.

So, let’s declare a simple interface

interface void INotificationSender
{
    void SendNotification(string userContactItem, string message);
    string ContactItemType
}

And let’s change the UserNotificationService to use the interface we declared

public class UserNotificationService
{
    private INotificationSender _notificationSender;

    // Constructor with Dependency Injetion
    public void UserNotificationService(INotificationSender _notificationSender)
    {
        _notificationSender = _notificationSender
    }

    public void NotifyUser(User user, string message)
    {
        var contactItem = user.GetContactItem(_notificationSender.ContactItemType);
        _notificationSender.SendNotification(contactItem, message);
    }
}

Now instead of depending on a specific implementation we declare an interface and require that any NotificationService that wants to be used by us, to implement that interface. We now are in control of a specific part of the implementation instead of the other way around. Of course, we have moved some tasks to the caller of the notification service, where the correct instance of INotificationService must be provided, but this is not our problem, we are currently writing the UserNotificationService (well, I am exaggerating a bit, we should care about being other team members, but we will see later how we are going to solve this issue.)

Thomas Sarmis
Software Engineer

I am a software engineer / system architect with experience in gaming (gambling)

Related