Schlagwort: design patterns

  • 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.

  • Inversion of Control – Part I

    Why am I talking about Inversion of Control? I will use Spring (Boot) later on to build services. Spring is based on Dependency Injection. Dependency Injection is one possible implementation of Inversion of Control. Hence, it is reasonable to understand the basics of Inversion of Control (IoC) and how it can be implemented. Before I get to the Sprint implementation, I will explain the concept, the Design Patterns that use IoC and will write about Dependency Injection in Spring in a subsequent blog post.

    What is Inversion of Control?

    When you write an application (code) you are fully in control of the flow.

    public static void main(String args[]) {
    
        while (true) {
            Scanner scan = new Scanner(System.in);
            String text = scan.nextLine();
            System.out.println(text);
        }
    }
    

    However, quite often the control is handed over to a framework that is in control of your code.

    Take for example Java Servlets, that I explained in an earlier blog post. In your implementation of the HttpServlet class, you implement a method such as doGet() which handles HTTP GET requests. This method is called whenever a request is made to an URL you specify in your web.xml. Here the servlet container takes control of your class. This allows the elegant implementation of different Servlets that are all controlled the same way.

    public class HelloServlet extends HttpServlet {
      private static final long serialVersionUID = 1L;
    
      public void init() throws ServletException {
        System.out.println("init()");
        super.init();
      }
    
      protected void doGet(HttpServletRequest req, HttpServletResponse resp)
          throws ServletException, IOException {
        System.out.println("doGet()");
      }
    }

    Another example is Event Handling, e.g. in JavaScript. You can bind JavaScript code to an event on an element, e.g. object.addEventListener("click", myScript); Once you click on the element, the JavaScript Engine of your Browser will call myScript.

    document.getElementById("submitButton").addEventListener("click", submitForm);
    
    function submitForm() {
       var name = document.getElementById("name");
       // read elements and call service()-method
    }

    Basically, IoC separates what code is executed from when it is executed. Both parts know as little as possible about each other – there only connection is an interface or abstract class provided by the controlling code. Furthermore, IoC reduces code duplication and encourages the use of Interfaces instead of implementations.

    Implementations of Inversion of Control

    Some of the well-known Design Patterns by the GoF (Gang of Four –  E. Gamma, R. Helm, R. Johnson and J. Vlissides) implement the Inversion of Control, such as the Observer, Strategy, Factory or the Template design pattern. Other examples are the Service Locator or Dependency Injection. In the next part, we will take a further look at Dependency Injection. In the last part, we will see how Dependency Injection is used with the Spring Framework.