Dorokhov.codes
Dependency injection
Dependency injection is a design pattern in which an object or function receives other objects or functions that it depends on.
The dependencies required by a component are not created or managed by the component itself. Instead, they are provided or “injected” into the component from the outside.
There are three common types of dependency injection:
- Constructor Injection: Dependencies are provided to a class through its constructor. The class declares its dependencies as parameters in the constructor, and an external entity is responsible for creating the instances of these dependencies and injecting them when creating an instance of the class.
- Setter Injection: Dependencies are set on the class using setter methods. The class provides setter methods for each dependency, and an external entity calls these methods to provide the required dependencies after creating an instance of the class.
- Interface Injection: The class implements an interface that declares methods for setting the dependencies. An external entity calls these methods to provide the required dependencies after creating an instance of the class.
By using dependency injection, you can easily replace or swap dependencies without modifying the component that uses them. This makes it easier to change the behavior of your application and improve its testability. It also helps to identify and manage dependencies explicitly, making the codebase more maintainable and reducing tight coupling between components.
Containers
Dependency injection frameworks or containers can automate the process of resolving and injecting dependencies, making it even easier to apply the pattern in large-scale applications. These frameworks can manage the lifecycle of objects, handle complex dependency graphs, and provide additional features like dependency configuration and resolution based on annotations or configuration files.
Overall, the dependency injection pattern promotes modular, flexible, and maintainable code by decoupling components and allowing their dependencies to be injected from external sources.
Types of DI
There are three main ways in which a client can receive injected services:
- Constructor injection, where dependencies are provided through a client’s class constructor.
- Setter injection, where the client exposes a setter method which accepts the dependency.
- Interface injection, where the dependency’s interface provides an injector method that will inject the dependency into any client passed to it.
Without dependency injection:
public class Client {
private Service service;
Client() {
// The dependency is hard-coded.
this.service = new ExampleService();
}
}
Constructor injection:
public class Client {
private Service service;
// The dependency is injected through a constructor.
Client(Service service) {
if (service == null) {
throw new IllegalArgumentException("service must not be null");
}
this.service = service;
}
}
Setter injection:
public class Client {
private Service service;
// The dependency is injected through a setter method.
public void setService(Service service) {
if (service == null) {
throw new IllegalArgumentException("service must not be null");
}
this.service = service;
}
}
Interface injection:
public interface ServiceSetter {
public void setService(Service service);
}
public class Client implements ServiceSetter {
private Service service;
@Override
public void setService(Service service) {
if (service == null) {
throw new IllegalArgumentException("service must not be null");
}
this.service = service;
}
}
public class ServiceInjector {
private Set<ServiceSetter> clients;
public void inject(ServiceSetter client) {
this.clients.add(client);
client.setService(new ExampleService());
}
public void switch() {
for (Client client : this.clients) {
client.setService(new AnotherExampleService());
}
}
}
public class ExampleService implements Service {}
public class AnotherExampleService implements Service {}