Constructor Injection, Property Injection or Method Injection
In software engineering, dependency injection is a design pattern that involves providing an object with its dependencies, rather than having the object create or manage them itself. There are three main types of dependency injection: constructor injection, property injection, and method injection.
Constructor injection
Constructor injection involves passing the dependencies to the object’s constructor, so that the object can be instantiated with its dependencies already in place. This is often considered the most explicit and straightforward way of injecting dependencies, since it makes the dependencies of the object explicit and requires the caller to provide them at the time the object is created.
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
Property injection involves setting the dependencies of an object using its public properties, either directly or via a setter method. This is a less explicit approach than constructor injection, since it doesn’t require the caller to provide the dependencies at the time the object is created. However, it can still make the dependencies of the object clear and easy to manage.
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. Instead of creating or managing the UserRepository
itself, the UserService
uses property injection to receive the UserRepository
from the caller. This is done by providing a userRepository
property, which the caller can use to set the UserRepository
on the UserService
before calling any other methods. The UserService
then checks if the userRepository
property is set before using it, and returns a default value if it is not set. 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.
Method injection
Method injection involves passing the dependencies to a specific method of the object, rather than to the object’s constructor or properties. This can be useful in situations where an object needs to use a dependency only for a specific operation, rather than for its entire lifetime. It can also make it easier to test objects, since the dependencies can be mocked or stubbed out in test cases.
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)
}
}
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 method injection to receive the UserRepository
from the caller. This is done by providing a setUserRepository
method, which the caller can use to pass the UserRepository
to the UserService
before calling any other methods. The UserService
then stores the UserRepository
in a private property, and checks if it is set before using it in the getUserById
method. 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.
Conclusion
Overall, the choice of which type of dependency injection to use will depend on the specific needs and requirements of your application. Each type has its own advantages and disadvantages, and the right choice will depend on the context in which the object is being used.
Originally published at https://needone.app on December 11, 2022.