Tag Archives: contructor injection

A BEGINNER’S GUIDE TO DEPENDENCY INJECTION!!

Heya people.!

In my last post I discussed about Singletons, its advantages and disadvantages, why it is considered an anti-pattern and stated DI as a better option.In this blog, I will be proving my statement along with going through DI basics and its key benefits.

But, to start with our discussion, we first need to understand the importance of DI.

For example, if we want to make tea for our guests. We will need to collect all the ingredients such as tea leaves, water, kettle , sugar and milk all by ourselves and then put the kettle on the gas to boil water, add tea leaves, milk and sugar and let the mixture boil before we serve it. For all this time, our guests will be waiting for us to get free and meet them. Will it then not be better to have a helping hand who knows better than us to make tea, who will make tea and serve us. If the latter solution excites you, then ladies and gentlemen, welcome to the world of DEPENDENCY INJECTION!

In the above example, if we take tea as an object, then DI help us in initializing the tea object and provide the object with all the necessary resources (i.e in our case ingredients). So basically, DI provides us with what an object needs to get initialized.

Dependency Injection is all about following 3 topics:

  1. Dependency Inversion Principle (DIP)]
  2. Inversion of Control (IOC)
  3. Dependency Injection

DEPENDENCY INVERSION PRINCIPLE

It is a software design pattern that states 2 principles:

  • High-level modules should not depend on low-level modules. They both should depend on abstractions.
  • Abstractions should not depend on details. Instead details should depend on abstractions.

To explain the above principle, let us take a very simple example.

Let us suppose, we have a watcher class that logs a message when it is notified about an event. The class will somewhat look like below:

class EventLog{
    public void log(String msg){
               //log message here
      }
 }

class Watcher{
        EventLog eventLog=null;

        public void notify(String msg){
        if(eventLog==null)
              eventLog=new EventLog();
        eventLog.log(msg);
    }
}

The example given shows no problem at first, but if looked closely violates DI principle. Let us know why!

In the above example, the high level class (Watcher class) depends on the low level class (EventLog class). Also, it depends on the concrete implementation of the low-level class, which completely violates both the principles of DIP.

Also, if at a later stage, requirement arises to extend the functionality of the watcher class i.e probably to send an email or a sms or both, to the designated person every time an event is logged, we’ll have to keep adding concrete implementations in the Watcher class. This will make the code coupled and unmanageable.

class EventLog{
      public void log(String msg){
              //log message here
          }
   }

class Email{

public void email(String msg){
     //email message here
  }
}

class Watcher{

     EventLog eventLog=null;
     Email email=null;

     public void notify(String msg){
     if(eventLog==null)
             eventLog=new EventLog();
      eventLog.log(msg);

     if(email==null)
            email=new Email();
     email.email(msg);
  }
}

Hence to avoid the above situation IOC was introduced.

INVERSION OF CONTROL

It is a mechanism using which we can make the higher level modules depend on abstractions rather than concrete implementations of lower level modules.

Basically we invert the control flow of our code to conform the DIP.

So, for the above example, we make all our concrete classes (EMAIL,SMS and EventLog) implement a common interface or abstract class CommInt consisting of a method actOnNotification().


abstract class CommInt{
       public abstract void actOnNotificstion(String msg);
}

class EventLog extends CommInt{
          public void log(String msg){
              //log message here
        }

     @Override
     public void actOnNotificstion(String msg) {
              log(msg);
     }
}

class Email extends CommInt{
        public void email(String msg){
              //email message here
      }

      @Override
      public void actOnNotificstion(String msg) {
            email(msg);
     }
}

class Watcher{
     CommInt commInt=null;
   
     public void notify(String msg){
       if(commInt==null)
          commInt=new EventLog();

       commInt.actOnNotificstion(msg);

       if(commInt==null)
          commInt=new Email();

        commInt.actOnNotificstion(msg);
    }
}

In the above example, although our concrete classes now depend on abstractions, still to create a concrete type and to assign it to this abstraction we, still have to create the object in our Watcher class, that takes us where we started.

Hence, our third and final topic DEPENDENCY INJECTION comes into play.

DEPENDENCY INJECTION

DI’s main motive is to reduce coupling between classes through injecting concrete implementations of a class that is using abstraction.

There are 3 ways to inject the same:

1. Constructor Injector: We pass the object of concrete class in constructor of dependent class. It is the most widely used.


public class Launcher {

 public static void main(String[] args) {
   Email email=new Email();
   Watcher watcher=new Watcher(email);
   watcher.notify("This Watcher class object sends email for its whole lifetime");

   EventLog eventLog = new EventLog();
   Watcher watcher1 = new Watcher(eventLog);
   watcher1.notify("This Watcher class object logs events for its whole lifetime");

   }
}

abstract class CommInt{
   public abstract void actOnNotificstion(String msg);
}

class EventLog extends CommInt{
   public void log(String msg){
      //log message here
   }

   @Override
   public void actOnNotificstion(String msg) {
     log(msg);
   }
}

class Email extends CommInt{
   public void email(String msg){
     //email message here
   }

   @Override
   public void actOnNotificstion(String msg) {
     email(msg);
   }
}

class Watcher{
   CommInt commInt=null;
   Watcher(CommInt commInt){
     this.commInt=commInt;
   }

   public void notify(String msg){
     commInt.actOnNotificstion(msg);
   }
}

It is useful when we know the instance of the dependent class and will us the same concrete class for its lifetime.

2. Method Injector: We pass object of concrete class into the method of dependent class.


public class Launcher {

  public static void main(String[] args) {
    Email email=new Email();
    Watcher watcher=new Watcher();
    watcher.notify(email,"This Watcher class object first sends email.");
    
EventLog eventLog = new EventLog();
    watcher.notify(eventLog,"and then logs events.!");
  }
}

abstract class CommInt{
  public abstract void actOnNotificstion(String msg);
}

class EventLog extends CommInt{
  public void log(String msg){
    //log message here
  } 

  @Override
  public void actOnNotificstion(String msg) {
    log(msg);
  }
}

class Email extends CommInt{
   public void email(String msg){
     //email message here
   }

    @Override
    public void actOnNotificstion(String msg) {
       email(msg);
    }
}

class Watcher{
      CommInt commInt=null;

      public void notify(CommInt commInt,String msg){
      this.commInt=commInt;
      commInt.actOnNotificstion(msg);
   }
}

It is useful when we need to pass separate concrete class on each invocation.

3. Property Injector: We pass object via a setter property that was exposed by the dependent class. It is used less frequently.


public class Launcher {

    public static void main(String[] args) {
        Email email = new Email();
        Watcher watcher = new Watcher();
        watcher.notify("This Watcher class object first sends email.");

        EventLog eventLog = new EventLog();
        watcher.set(eventLog);
        watcher.notify("and then logs events.!");

    }
}

abstract class CommInt {

    public abstract void actOnNotificstion(String msg);
}

class EventLog extends CommInt {
    public void log(String msg) {
        //log message here
    }

    @Override
    public void actOnNotificstion(String msg) {
        log(msg);
    }
}

class Email extends CommInt {
    public void email(String msg) {
        //email message here
    }

    @Override
    public void actOnNotificstion(String msg) {
        email(msg);
    }
}

class Watcher {

    CommInt commInt = null;

    public CommInt get() {
        return commInt;
    }

    public void set(CommInt commInt) {
        this.commInt = commInt;
    }

    public void notify(String msg) {
        get().actOnNotificstion(msg);
    }
}

It is useful when selection of concrete and invocation of method are in different places.

All the 3 injections are best for one level of dependencies. For a chained or nested dependencies, IOC containers (like Spring, hibernate, etc) come into play. These help us to map the dependencies easily when we have chained or nested dependencies.

KEY BENEFITS

  • maintainable code
  • de-coupled code
  • extendable code
  • move dependency resolution from compile time to run time.
  • centralized configuration
  • improves application testing

In our above example of watcher class,

  • We need to change our launcher to use Email class instead of SMS or EventLog class.
  • Independent unit cases can be created to test all the concrete classes with our Watcher class.

DRAWBACK

The main drawback of dependency injection is that using many instances together can become a very difficult if there are too many instances and many dependencies that need to be resolved.

DI better than Singletons

Singletons maintain a global state which is hard to test. Also, they cannot be extended i.e you can’t change the implementation used by one class without changing it for all of them. For the said reasons, DI will always be a better option! 😀

Hope, the basic concept regarding DI must’ve been cleared. !! For any queries, leave a comment.! 🙂

Advertisements