Skip to content

abhishek-bits/dependency-injection

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Dependency Injection

Youtube Tutorial

  • In any object-oriented language, classes and objects are the foundations of any functionality you can think of.
  • The relationships between these classes and objects, make it possible to extend and reuse some of these functionalities.
  • The way that we choose to build these functionalities (or dependencies), determine how decoupled and reusable our code will be.
public interface Food { }
public class Pizza implements Food { }
public class Burger implements Food { }
public class Chef {

    private Food food;

    // Passing the dependency to the constructor
    // of the dependent object
    public Chef(Food food) {
        this.food = food;
    }

    public void prepareFood() {
        // do something with the food object
    }
}
public class Main {
    public static void main(String[] args) {

        // Applying Dependency Injection
        Chef pizzaChef = new Chef(new Pizza());

        // Applying Dependency Injection
        Chef burgerChef = new Chef(new Burger());
    }
}

The Dependency was injected into the object instead of being created inside it.

  • We decoupled the construction of Chef class from the construction of its dependencies.
  • Now, it is no longer the dependent class's responsibility to figure out what is changing.

Types of Dependency Injection

Constructor Injection

  • MOST RECOMMENDED One!
  • Dependencies are provided through a class constructor.

Setter Injection

The Dependency is injected via the setter method.

public class ClassA {
    
    private ClassB classB;
    
    public CLass() {}
    
    public void setClassB(ClassB classB) {
        this.classB = classB;
    }
}
public class Main {
    public static void main(String[] args) {
        ClassA classA = new ClassA();
        classA.setClassB(new ClassB());
    }
}

Disadvantages: Hides the dependencies

  • By reading the constructor signature we cannot identify that there is a dependency right away, leading to NullPointerException at runtime.

Field Injection

The Dependency is injected via the field.

public class ClassA {
    
    public ClassB classB;
    
    public ClassA() {}
}
public class Main {
    public static void main(String[] args) {
        ClassA classA = new ClassA();
        classA.classB = new ClassB();
    }
}

Disadvantage: Hides the dependency

  • Adds complexity due to the mutation or reflection required.

Inversion of Control

This is the D of SOLID Principles; Dependency Inversion Principle.

  • It is the principle in software engineering which transfers the control of objects or portions of a program to a framework (or library).
  • Enables a framework to take control over the flow of a program and make calls to our custom code.
  • To achieve this, frameworks use abstraction, hence to add an extra behavior, we extend the classes of the framework.
With Without
// Depends on the abstraction
// Loosely coupled code
public class Chef {
    private Food food;
    public Chef(Food food) {
        this.food = food;
    }
}
// Depends on the implementation
// Tightly coupled code
public class Chef {
    private Food food;
    public Chef() {
        food = new Burger();
    }
}

A class should concentrate on fulfilling its responsibilities and not on creating objects that are required to fulfil them. Such objects should be provided by Dependency Injection.

Advantages

  • Reduces boilerplate and duplicate code, as the dependencies are provided by the injector.
  • Follows Open-Closed principle.
  • Program becomes easier to test as the dependencies can be isolated and mocked.
  • Allows components to communicate through contracts.

Before Applying DI

  • Using Dependency Injection will most likely make the code worse.
  • Should only be used whenever it is necessary.
  • Consider if we can reduce or eliminate dependencies or if mocking will facilitate our testing.

Problem 1: Leaking Classes

Suppose ClassB is used only internally by ClassA.

RECOMMENDED Approach: Avoid Dependency Injection

public class ClassA {
    
    public ClassA() {}
    
    public void doStuff() {
        // instantiate ClassB
        // do something with it
        // Clients don't care about it.
    }
    
    private static ClassB {
        // properties and methods here
    }
}

INVALID Approach: Applies Dependency Injection

public class ClassA {
    
    private ClassB classB;
    
    public ClassA(ClassB classB) {
        this.classB = classB;
    }
    
    public void doStuff() {
        // do something with classB
    }
}
  • Here, we have exposed the implementation detail of ClassA
  • Now, ClassB has to exist in some other place as well.

This leads to an overall worse architecture with leaking concerns.

Problem 2: Mocking

public class ClassATest {
    
    @Test
    public void test() {
        ClassA classA = new ClassA(mock(new ClassB()));
        // test something
    }
}

If we mock ClassB as shown, our test will definitely pass. But, we will never be sure that the actual production code of ClassA will work with the actual production code of ClassB as all we used was a mock.

About

Fundamentals of Dependency Injection in Java

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages