Skip to content

Dependency Injection

The textbook definition:

In software engineering, dependency injection is a technique in which an object receives other objects that it depends on, called dependencies. Typically, the receiving object is called a client and the passed-in (‘injected’) object is called a service.

https://en.wikipedia.org/wiki/Dependency_injection

Let’s Pretend

Who you are:

A Senior Engineer at Amazon, in the early days.

What you’re doing:

Implementing an event-driven system for “Delivered” notifications.

The problem:

One customer wants an SMS. Another customer wants an Email and Push notification.

To successfully apply this pattern you need 4 things:

1. An Interface to define the required functionality.

interface INotifyService
{
    public void Send(string userId);
}

2. A Service implementing the Interface.

class EmailService : INotifyService
{
    public void Send(string userId)
    {
        // Send an Email...
    }
}
class PushService : INotifyService
{
    public void Send(string userId)
    {
        // Send a Push notification...
    }
}
class SmsService : INotifyService
{
    public void Send(string userId)
    {
        // Send an SMS message...
    }
}

3. A Client that accepts the Interface. (new NotificationClient())
     We’ll look at two implementations later.

4. An Injector to pass the appropriate Service to the Client. (Function.cs)
     We’ll look at two implementations later.

The Intuitive Way

We just received a fresh batch of events!

We’ll use a new NotificationClient() to send 3 notifications to 2 users.

var notificationClient = new NotificationClient();

// User One is configured to receive Email
notificationClient.SendNotification("user-one-id", "email");

// User Two is configured to receive both Sms and Push
notificationClient.SendNotification("user-two-id", "sms");
notificationClient.SendNotification("user-two-id", "push");

Each call to SendNotification() initializes a Service to notify a user.

class NotificationClient
{
    public void SendNotification(string userId, string type)
    {
        // Initialize a Service based on the "type".
        switch (type)
        {
            case "email":
                new EmailService().Send(userId);
                break;
            case "push":
                new PushService().Send(userId);
                break;
            case "sms":
                new SmsService().Send(userId);
                break;
        }
    }
}

Notice how the Client has to:
1. Figure out which Service to use. (switch (type))
2. Know about all of the different Services that are available. (EmailService, etc)
3. Invoke the appropriate Service. (eg new EmailService().Send(userId))

This is actually the “intuitive” way to do it for most people. This is how Senior developers wrote code for a long time, and it’s the natural way that Junior developers want to write code now. The main idea behind Dependency Injection didn’t start to grow until after 1994, and the actual term Dependency Injection didn’t really become a thing until after 2003.

The Inverted Way

What if we did something crazy? Instead of leaving it up to the Client, we can inject the specific Service that we want to use.

var notificationClient = new NotificationClient();

// We inject the specific dependency we need.
var emailService = new EmailService();
notificationClient.SendNotification("user-one-id", emailService);

var smsService = new SmsService();
notificationClient.SendNotification("user-two-id", smsService);

var pushService = new PushService();
notificationClient.SendNotification("user-two-id", pushService);

Now NotificationClient.cs doesn’t need to know anything about any specific Service. Sms, Email, Push, maybe a SlackService in the future? It doesn’t care!

class NotificationClient
{
    public void SendNotification(string userId, INotifyService notifyService)
    {
        // Prepare the user details
        notifyService.Send(userId);
    }
}

Notice how the Client only has to:
1. Handle the Interface. (notifyService.Send(userId))

Now the Client can focus on business logic.

Advantages of Dependency Injection

  1. The Client no longer cares about the Service implementation. We could create 100 implementations of INotifyService, and we wouldn’t need to update NotificationClient.
  2. We are able to mock the Interface for effective unit testing. Since we’re injecting our dependencies, one of those implementations of INotifyService could return dummy data, making it very easy to test.

Dependency Injection Techniques

There are 3 common injection techniques. It will be years before you’re confident saying exactly why you want to use a specific technique.

  • Constructor – when the class is initialized.
  • Method – when an action is invoked.
  • Property – set it at your convenience.

In our example above we used Method injection.

Dependency Injection Containers

We’ve been looking at manual dependency injection.

It is common in any large app to utilize a DI Container, which automates the Injector role.

I recommend adding the subject to your list.

Summary

Dependency Injection is fundamental to modern programming. It’s as ubiquitous as OOP itself.

Only practice will lead to a proper understanding and mastery of the topic.

Prefer video? I have an accompanying video on the topic (below).

Subscribe to be notified

3 thoughts on “Dependency Injection”

  1. Hope you guys enjoyed the post! Definitely check out the video, subscribe, and let me know if you have any thoughts. I’ve got some great posts and videos coming up on becoming a Senior Software Engineer, and what will be expected of you!

Leave a Reply to Margaret J CraddockCancel reply

Discover more from Hunter Web Apps

Subscribe now to keep reading and get access to the full archive.

Continue reading