What is DI in java ? Benefits and usage ( Dependency Injection in Java)

/
1 Comments

Lets assume we want to go to a trip to Paris.What we will do ? You have to plan everything

1) Book your flights
2)Book your hotels to stay
3)Book your car rental for travelling in Paris
4)Book your amenity pass for visiting different places

assume that you have done all the booking advance and there is an email from flight authority that the flight scheduled is cancelled and it is rescheduled to next day. What you will do?

you have to reschedule all the above bookings to next day and to communicate all the different agencies(hotel/car rental/amenity pass center). This is a pain for you by this unexpected channges happened right?  Now think about a situation that your booking is done by a travel agency including 
all the booking and because of flight delay they rescheduled everything by themself without charging
any extra dollar from you and you will be able to travel on the same day without any delay !!

How it will feel ? nice right. You dont bother about the rescheduling and re booking of all your tickets, travel agency will take care of everything. Would that be better than booking tickets by ourselves? If yes, then welcome to dependency injection.


The whole example is easily clonable or download from GitHub repository

Dependency injection is a framework that takes care of creating objects for us without having to worry about providing the right tickets so to say.

Explanation

Consider we have a class Student and this class need to write a test need help on another class WriteTest() which will help us to write a test for a student.
   package com.johnjustin;

public class AttendExam {
 
 public void writeTest(final String ExamName, final String subject) {
     System.out.printf("writeTest : %s, %s%n", ExamName, subject);
   }

}

package com.johnjustin;

public class Student {
  private AttendExam attendExam = new AttendExam();

  public void writeTest(){
    attendExam.writeTest("Engineering", "Mathematics");
  }
}


This is the simplest way to fulfill the requirement but we know that this has some limitations

1)There is a tight dependency on AttendExam class on Student class.
2) if there is a design change planned on writeTest() method with different parameters and there might be changes happened for different types of exams like online exam and offline written exam
we should go to all the places wherever this AttendExam written will be changed.
3)After sometimes there is new requirement came and we have to implement 2 types of tests.Online assessment and offline written test at this scenario it will be difficult for us to maintain same AttendExam class.
These limitations can be improved by changing the way we think and refactor our code in a modular way.We can avoid these limitations by implementing dependency injection by modular way.


1)Create Interface
2)Implement Interface
3)integrate DI using any(Spring/Guice) framework

these three steps will help to decouple the tight dependencies between classes and it will be easy to migrate newer version of changes and updation of existing features without changing the current codes.

Create Interface

We will create an interface AssessmentService where we define a common method writeTest with parameters.So there must be different types of test either online test or written test both classes will inherit this AssessmentService and implement writeTest for their own exam implementation.
package com.johnjustin;

public interface AssessmentService {
 
 void writeTest(String subject, String testName);

}

Implement Interfaces

We have listed some limitations above and there is requirement to implement 2 types of tests online assessment and offline written tests. We have created 2 different classes OnlineExamService and WrittenExamService for these tests and it will inherit the feature of AssessmentService interface's writeTest method.
    package com.johnjustin;

/**
 * @author John Justin
 *
 */
public class WrittenExamService implements AssessmentService{
 
 public void writeTest(final String ExamName, final String subject) {
     System.out.printf("Written Exam for : %s, %s%n", ExamName, subject);
   }


}
    package com.johnjustin;

/**
 * @author John Justin
 *
 */
public class OnlineExamService implements AssessmentService{
 
 public void writeTest(final String ExamName, final String subject) {
     System.out.printf("Online Exam for : %s, %s%n", ExamName, subject);
   }

}
Use interfaces to reduce loosely couple the classes. We have to use this AssessmentService in our Student class and implement required type of assessment(Online/Written) according to the need.This is a key element in the design. It improves modularity, extendibility.
  
     package com.johnjustin;
import com.johnjustin.AssessmentService;
import com.google.inject.Inject;

/**
 * @author John Justin
 *
 */
public class Student {

 private final AssessmentService assessmentService;
 
 @Inject
 private Student(AssessmentService assessmentService){
  
  this.assessmentService = assessmentService;
 } 
 
 public void attendExam() {
   assessmentService.writeTest("Engineering", "Mathematics");
    }
  
 
}

 
The Student class is not dependent on any implementation, but on a service defined by an interface. This means that we can use the Student class without having to worry about the underlying implementation of the assessment service. Furthermore, different Student instances can be instantiated using different assessment services.

Integrate DI using any(Spring/Guice) framework


dependency injection can help us initializing objects and provide these objects all the necessary resources . For example, the Student class requires an instance of AssessmentService. The dependency injection framework( Guice here) will provide that for us. So to create an instance of Student class.

The dependency injection framework(Guice) will create an instance of the Student class and provide an instance of the AssessmentServiceto the Student object.

How to integrate google Guice dependency frame work in our programe?
how does the dependency injection framework knows how to initialise the AssessmentService?

Usage of Dependency Injection requires that the software we write to declare the dependencies, and lets the framework or the container work out the complexities of service instantiation, initialization, sequencing and supplies the service references to the clients as required. To decouple Java components from other Java components the dependency to a certain other class should get injected into them rather that the class itself creates / finds this object.    

The general concept between dependency injection is called Inversion of Control. A class should not configure itself but should be configured from outside.          

 We need to tell the dependency injection framework how to create an instance of AssessmentService. With Guice we do that by creating a module (a class that extends AbstractModule class) as illustrated below. 

We should tell the Guice dependency framework to how to create an instance of AssessmentService class.We should add an annotation in Student class in order to allow the dependency injection framework to inject the necessary parameters.
 
     package com.johnjustin;

import com.google.inject.AbstractModule;

/**
 * @author John Justin
 *
 */
public class ProjectModule extends AbstractModule {
 
 @Override
   protected void configure() {
     bind(AssessmentService.class).to(OnlineExamService.class);
   }

}
        package com.johnjustin;
import com.johnjustin.AssessmentService;
import com.google.inject.Inject;

/**
 * @author John Justin
 *
 */
public class Student {

 private final AssessmentService assessmentService;
 
 @Inject
 private Student(AssessmentService assessmentService){
  
  this.assessmentService = assessmentService;
 } 
 
 public void attendExam() {
   assessmentService.writeTest("Engineering", "Mathematics");
    }
  
 
}
Now we can create the main class and create its injection in it.
package com.johnjustin;

import com.google.inject.Guice;
import com.google.inject.Injector;

/**
 * @author  John Justin
 *
 */
public class Main {
 
 
  public static void main(final String[] args) {
      final Injector injector = Guice.createInjector(new ProjectModule());
      final Student student = injector.getInstance(Student.class);
      student.attendExam();
    }

}

Advantages of DI


Dependency injection is a pattern used to create instances of classes that other classes rely on without knowing at compile time which implementation will be used to provide that functionality.

Easily changing the the exam type from online exam service to written exam service.We only need to change the ProjectModule class to map the AssessmentService class to the WrittenExamService class as highlighted below.This one change will affect all classes initialised with the dependency injection framework without having to change any of these classes.
package com.johnjustin;

import com.google.inject.AbstractModule;

/**
 * @author John Justin
 *
 */
public class ProjectModule extends AbstractModule {
 
 @Override
   protected void configure() {
     bind(AssessmentService.class).to(WrittenExamService.class);
   }

}

The whole example is easily clonable or download from GitHub repository.Enjoy !!!!!








You may also like

1 comment: