Inversion of Control – Part II – Dependency Injection


In the first part, I described what Inversion of Control (IoC) is and how it can be implemented using the classical GoF design patterns. In this post, I will explain another implementation of IoC: Dependency Injection.

I assume that you are familiar with programming to an interface (or abstract class) rather than classes, as you can easily switch the implementation (aka polymorphism, programming by contract).

I will start right way with an example: Let us assume we want to send an alert via different mechanisms, such as an E-Mail and a Slack message. The Services that send a message, have a common interface: MessageService; the implementations are EmailMessageService and SlackMessageService. I won’t include that code for sending actual messages, as this would become too complicated. Instead, I will simply log the result to the console.

package com.vividbreeze.designpattern.di;

public interface MessageService {
    void sendMessage (String subject, String message, String receiver);
}
package com.vividbreeze.designpattern.di;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class EmailMessageService implements MessageService {

    private static final Logger LOG = LoggerFactory.getLogger(EmailMessageService.class);

    @Override
    public void sendMessage(String receiver, String subject, String message) {

        // Code to send an E-Mail message

        LOG.debug("E-Mail successfully sent.");
        LOG.debug("Subject: '" + subject + "'");
        LOG.debug("Receiver: '" + receiver + "'");
        LOG.debug("Message:");
        LOG.debug(message);
    }
}
package com.vividbreeze.designpattern.di;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SlackMessageService implements MessageService {
    private static final Logger LOG = LoggerFactory.getLogger(SlackMessageService.class);

    @Override
    public void sendMessage(String receiver, String subject, String message) {

        // Code to send message to a Slack Channel

        LOG.debug("Slack Message successfully sent.");
        LOG.debug("Topic: '" + subject + "'");
        LOG.debug("Receiver or Channel: '" + receiver + "'");
        LOG.debug("Message:");
        LOG.debug(message);
    }
}

I further have an abstract class for any kind of service that is responsible for processing the alert, such as sending a message to the correct receiver, e.g. when the disk is full an admin is notified via e-mail; when an exception in the code occurs during working hours, the developers are informed via Slack.

package com.vividbreeze.designpattern.di;

public abstract class AlertService {

    protected MessageService messageService;

    public AlertService(MessageService messageService) {
        this.messageService = messageService;
    }

    public abstract void processAlert(String receiver);
}

 

Constructor-Based Dependency Injection

We can inject the dependency to the MessageService in the constructor. I will continue the example above and subclass the AlertService.

package com.vividbreeze.designpattern.di;

public class DiscFullAlertService extends AlertService {

    public static final String ALERT_TOPIC = "Alert on disk /gdalogs1 almost full.";
    public static final String ALERT_MESSAGE = "disk /gdalogs1 has reached 90% of its capacity.";


    public DiscFullAlertService(MessageService messageService) {
        super (messageService);
    }

    @Override
    public void processAlert(String receiver) {
       messageService.sendMessage(ALERT_TOPIC, ALERT_MESSAGE, receiver);
    }
}

The following code shows how to put the pieces together and run the application.

package com.vividbreeze.designpattern.di;

public class FireAlert {

    public static void main (String args[]) {

        String receiver = "ops@vividbreeze.com";

        new DiscFullAlertService(new EmailMessageService()).processAlert(receiver);
    }
}

In the example above, the special AlertService or it subclasses do not know which MessageService will send the message (EmailMessageService or SlackMessageService) at runtime. So I will inject an instance of the appropriate Service when instantiating the DiscFullAlertService (in the constructor).

When I write a test for the DiscFullAlertService, the system running the test, might not be capable of sending Emails or Slack messages (e.g. the development environment). In this case, we have to mock these services without having to change existing code. As we are using Dependency Injection, this has become fairly easy. I can initialise the DiscFullAlertService with a MockMessageService, that does not actually send a message, but only logs it on the console.

package com.vividbreeze.designpattern.di;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Component
public class MockMessageService implements MessageService {

    private static final Logger LOG = LoggerFactory.getLogger(MockMessageService.class);

    @Override
    public void sendMessage(String subject, String message, String receiver) {
        LOG.debug("MockMessageService - subject: " + subject + ", receiver: " + receiver + ", message: " + message);
    }
}

The test will then look like this

package com.vividbreeze.designpattern.di;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class FireAlertTest {

    private static final Logger LOG = LoggerFactory.getLogger(FireAlertTest.class);
    private MessageService mockMessageService;

    @BeforeEach
    public void setUp() {
        mockMessageService = new MockMessageService();
    }

    @Test
    public void sendAlertMessage() {
        new DiscFullAlertService(mockMessageService).processAlert("test-ops@vividbreeze.com");
    }
}

 

Setter-Based Dependency Injection

We can also inject the dependency to the MessageService via a setter, instead of a constructor.

package com.vividbreeze.designpattern.di;

public abstract class AlertService {

    protected MessageService messageService;

    public void setMessageService(MessageService messageService) {
        this.messageService = messageService;
    }

    public abstract void processAlert(String receiver);
}
package com.vividbreeze.designpattern.di;

public class DiscFullAlertService extends AlertService {

    public static final String ALERT_TOPIC = "Alert on disk /gdalogs1 almost full.";
    public static final String ALERT_MESSAGE = "disk /gdalogs1 has reached 90% of its capacity.";

    @Override
    public void processAlert(String receiver) {
       messageService.sendMessage(ALERT_TOPIC, ALERT_MESSAGE, receiver);
    }
}
package com.vividbreeze.designpattern.di;

public class FireAlert {

    public static void main (String args[]) {

        String receiver = "ops@vividbreeze.com";

        AlertService alertService = new DiscFullAlertService();
        alertService.setMessageService(new EmailMessageService());
        alertService.processAlert(receiver);
    }
}
package com.vividbreeze.designpattern.di;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class FireAlertTest {

    private static final Logger LOG = LoggerFactory.getLogger(FireAlertTest.class);
    private MessageService mockMessageService;

    @BeforeEach
    public void setUp() {

        mockMessageService = new MockMessageService();
    }

    @Test
    public void sendAlertMessage() {

        AlertService alertService = new DiscFullAlertService();
        alertService.setMessageService(new EmailMessageService());
        alertService.processAlert("test-ops@vividbreeze.com");
    }
}

Remarks

So we have seen that dependencies can be easily set by other objects at runtime. You might run into some interesting discussions about Dependency Injection. The arguments I have heard so far is that Dependency Injection increases the number of classes and hence make the code more difficult to understand, especially if you have many dependencies. I concur, as I rather deal with many simple classes that are easier to understand, than a few complex classes. In addition, you have the flexibility of changing implementation, which becomes especially useful when testing implementations. Dependency Injection might have an effect on performance, but I consider it negligible.

In the subsequent blog post, I will explain how the Java Spring Framework can support you in using Dependency Injection.

Chris