What Is Dependency Injection Pattern

Dependency Injection

Dependency injection pattern is a software design pattern in which one or more dependencies (or services) are injected, or passed by reference, into a dependent object (or client) and are made part of the client’s state.

The dependency injection pattern separates the creation of a client’s dependencies from its own behavior, which allows program designs to be loosely coupled and to follow the dependency inversion and single responsibility principles.

This pattern directly contrasts the service locator pattern, which allows clients to know about the system they use to find dependencies.

An injection is the passing of a dependency (a service) to a dependent object(a client). The service is made part of client’s state. Passing the service to the client, rather than allowing a client to build or find the service, is the fundamental requirement of the pattern.

Related Posts:

In dependency injection only a single instance of a dependency is used.

Advantages of Dependency Injection

Because dependency injection pattern doesn’t require any change in code behavior it can be applied to legacy code as a refactoring. The result is clients that are more independent and that are easier to unit test in isolation using stubs or mock objects that simulate other objects not under test. This ease of testing is often the first benefit noticed when using dependency injection.

Dependency injection allows a client to remove all knowledge of a concrete implementation that it needs to use. This helps isolate the client from the impact of design changes and defects. It promotes reusability, testability and maintainability.

Dependency injection can be used to externalize a system’s configuration details into configuration files allowing the system to be reconfigured without recompilation. Separate configurations can be written for different situations that require different implementations of components. This includes, but is not limited to, testing.

Reduction of boilerplate code in the application objects since all work to initialize or set up dependencies is handled by a provider component.

Dependency injection allows concurrent or independent development. Two developers can independently develop classes that use each other, while only needing to know the interface the classes will communicate through. Plugins are often developed by third party shops that never even talk to the developers who created the product that uses the plugins.

Disadvantages of Dependency Injection

Dependency injection can make code difficult to trace (read), because it separates behavior from construction. This means developers must refer to more files to follow how a system performs.

Dependency injection typically requires more lines of code to accomplish the same behavior legacy code would.

Dependency injection diminishes encapsulation by requiring users of a system to know how it works and not merely what it does.

By applying Dependency Injection, your code becomes significantly simpler, easier to understand and test. An application which is made up of two or more classes that collaborate with each other to perform some business logic.

Traditionally each object is responsible for obtaining its own references to objects it collaborates with. This can lead to highly-coupled and hard to test.

For example, consider the below class

class Vehicle {
	private Truck truck = new Truck("Truck1");
	public void driveVehicle() {
		truck.drive();
	}
}

The class Vehicle instantiates a truck as this class needs a truck. So the class Vehicle depends on the class Truck. So the class Vehicle cannot carry its work without a Truck. Therefore, Vehicle has a dependency on Truck Truck.

The class Vehicle itself instantiates a Truck. Therefore the Vehicle class is said to satisfy its own dependencies.

When a class satisfies its own dependencies it automatically also depends on the classes it satisfies the dependencies with.

In this case Vehicle now also depends on Truck, and on the one hard-coded string value passed as a parameter to the Truck constructor.

You cannot use a different value for this string, nor use a different implementation of the Truck interface, without changing the code.

As you can see, when a class satisfies its own dependencies it becomes inflexible with regards to these dependencies. This is not a good design. This means, that if you need to change the dependencies you will have to change the code.

In this example, if you need to use a different truck or car, you will need to change the Vehicle class. If you have many classes implemented like this you will need to change them all. In addition, you cannot unit test the Vehicle class using a mock Truck. You can only use the Truck.

class Vehicle {
	public Vehicle(String name) {
		truck = new Truck(name);
	}
	public void driveVehicle() {
		truck.drive();
	}
}

Notice how the Truck instantiation is moved into a constructor. The constructor takes one parameter which is the string value needed by the Truck.

Though the Vehicle class still depends on this value, it no longer satisfies the dependency itself. It is provided by whatever class creating a Vehicle instance.

The value is said to be injected into the Vehicle constructor. Hence the term dependency injection.

It is now possible to change the name used by the Vehicle class, without changing the Vehicle class.

Dependency injection is not restricted to constructors. You can also inject dependencies using setter methods, or directly into public fields.

The Vehicle class can still be made more independent. It still depends on both the Truck class. There is no need for it to depend on more than the Car interface. This can be achieved by injecting a Car into the constructor instead of the string parameter. Here is how that looks:

class Vehicle {
	private Car car;
	public Vehicle(Car car) {
		this.car = car;
	}
	public void driveVehicle() {
		car.drive();
	}
}

Now the Vehicle class no longer depends on the Truck class, or the one string needed by the Truck constructor. You can now inject any truck or car into the Vehicle constructor.

Decoupling using Interfaces

A common technique is used to reduce coupling is to hide implementation details behind interfaces so that the actual implementation class can be swapped out without impacting the client class.

For example, suppose you were to create,

public interface Car {
	public void drive();
}

Then, you change the Truck class to implement this interface.

Class Truck implements Car {
	private String name;
	public Truck() {}
	public Truck(String name) {
		this.name = name;
	}
	public void drive() {
		System.out.ptintln("Driving a truck : " + name);
	}
}

and

Class Bus implements Car {
	private String name;
	public Bus() {}
	public Bus(String name) {
		this.name = name;
	}
	public void drive() {
		System.out.ptintln("Driving a bus : " + name);
	}
}

Now you can use Vehicle class to use any Car type whether it is a Truck or Bus.

This dependency injection pattern is continued all the way up to the layers of our application, from the lowest data accessing layers up to the user interface (if any).

Hope you got an idea about Dependency Injection design pattern.

Leave a Reply

Your email address will not be published. Required fields are marked *