Dependency Injection is a design pattern that is providing another object with its dependencies, instead of having the object created or managed themselves. There are three main types of dependency injection: constructor injection, property injection, and method injection.
Constructor injection
As the name indicated, constructor injection is passing the dependencies to the object's constructor. This is often considered the most explicit and straightforward way of injecting dependencies. The benefits of constructor injection is that all the injected dependencies are passed as parameters, which is clearly for future reference.
Example in Swift:
class UserService {
private let userRepository: UserRepository
init(userRepository: UserRepository) {
self.userRepository = userRepository
}
func getUserById(id: Long) -> User {
return userRepository.findById(id: id)
}
}
In this example, the UserService
class has a dependency on a UserRepository
object, which it uses to find users by their ID. Instead of creating or managing the UserRepository
itself, the UserService
uses constructor injection to receive the UserRepository
from the caller. This is done by providing a initializer that accepts a UserRepository
as a parameter, which the caller can use to pass the UserRepository
to the UserService
when creating a new instance of the class. This allows the UserService
to use the UserRepository
without having to create or manage it itself, making the code more modular and easier to test.
Property injection
Similarly, property injection is passing the dependencies of an object through its public properties, either directly or via a setter method. However, this is a less explicit approach than the above constructor injection, as it doesn't require the caller to provide the dependencies at the time the object is created.
class UserService {
var userRepository: UserRepository?
func getUserById(id: Long) -> User {
guard let userRepository = userRepository else {
return User()
}
return userRepository.findById(id: id)
}
}
Similar example to the Constructor injection
, the UserService
class has a dependency on a UserRepository
object, which it uses to find users by their ID. Rather than creating or managing the UserRepository
itself, the UserService
uses property injection to get the UserRepository
from the caller. All of this is done by providing a userRepository
property.
Method injection
Lastly, method injection will pass the dependencies to the target object by a method, instead of the object's constructor or properties. This can be useful in some situations such as an object needs to use a dependency only for a specific scope rather than for the entire lifetime.
Similar example in Swift:
class UserService {
private var userRepository: UserRepository?
func setUserRepository(userRepository: UserRepository) {
self.userRepository = userRepository
}
func getUserById(id: Long) -> User {
guard let userRepository = userRepository else {
return User()
}
return userRepository.findById(id: id)
}
}
As in the above example, the UserService
class has a dependency on a UserRepository
object. Instead of creating and managing the UserRepository
itself, the UserService
uses method injection to receive the UserRepository
from the caller. We are using a setUserRepository
method here, which calls setUserRepository
to pass the dependency and stores it in a private property, making the code more modular and easier to test.
Why type of dependency injection is better?
Overall, constructor injection, property injection, and method injection are three common ways to let you have your object dependency injected. However, to choose which type of dependency injection to use will much depend on the specific requirements of the program you worked on.