The three fundamental types of Dependency Injection (DI) in Kotlin and Java are:
- Constructor Injection
- Field (or Property) Injection
- Method (or Setter) Injection
Each type of DI handles injecting dependencies into a class in different ways. Let's explore these in detail:
1. Constructor Injection
With constructor injection, the dependencies are passed through a class constructor. This is the most common and preferred form of dependency injection because it makes the class more explicit and easier to test. The dependencies are required at the time of object creation.
Example in Kotlin:
kotlin
// Dependency class
class UserRepository {
fun getUser(id: String): User {
return User(id, "John Doe")
}
}
// Dependent class with Constructor Injection
class UserService(private val userRepository: UserRepository) {
fun getUser(id: String): User {
return userRepository.getUser(id)
}
}
// Creating objects with constructor injection
fun main() {
val userRepository = UserRepository() // Dependency
val userService = UserService(userRepository) // Injecting dependency
println(userService.getUser("123").name)
}
Example in Java:
java
// Dependency class
class UserRepository {
public User getUser(String id) {
return new User(id, "John Doe");
}
}
// Dependent class with Constructor Injection
class UserService {
private UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User getUser(String id) {
return userRepository.getUser(id);
}
}
// Creating objects with constructor injection
public class Main {
public static void main(String[] args) {
UserRepository userRepository = new UserRepository();
UserService userService = new UserService(userRepository);
System.out.println(userService.getUser("123").getName());
}
}
2. Field (Property) Injection
With field injection (in Kotlin, property injection), the dependency is directly assigned to a field or property after the object is constructed. It is common in Java frameworks like Spring when annotations such as @Autowired are used. This form is less preferred because it hides the dependencies from the constructor and makes the object harder to test.
Example in Kotlin (Using lateinit for Property Injection):
kotlin
class UserRepository {
fun getUser(id: String): User {
return User(id, "John Doe")
}
}
class UserService {
lateinit var userRepository: UserRepository // Injecting dependency later
fun getUser(id: String): User {
return userRepository.getUser(id)
}
}
fun main() {
val userRepository = UserRepository()
val userService = UserService()
// Field injection (manual in this case)
userService.userRepository = userRepository
println(userService.getUser("123").name)
}
Example in Java (Using @Autowired for Field Injection):
java
class UserRepository {
public User getUser(String id) {
return new User(id, "John Doe");
}
}
class UserService {
@Autowired
private UserRepository userRepository; // Injecting dependency automatically
public User getUser(String id) {
return userRepository.getUser(id);
}
}
In Java,
@Autowired(from Spring Framework) is used for automatic field injection.
3. Method (Setter) Injection
In method injection (or setter injection), dependencies are passed through public setter methods. This can be useful when dependencies are optional or changeable after object creation. Setter injection is common when the class has default dependencies, but optional ones can be set later.
Example in Kotlin (Setter Injection):
kotlin
class UserRepository {
fun getUser(id: String): User {
return User(id, "John Doe")
}
}
class UserService {
private var userRepository: UserRepository? = null
// Setter method for injecting dependency
fun setUserRepository(userRepository: UserRepository) {
this.userRepository = userRepository
}
fun getUser(id: String): User? {
return userRepository?.getUser(id)
}
}
fun main() {
val userRepository = UserRepository()
val userService = UserService()
// Injecting dependency using setter method
userService.setUserRepository(userRepository)
println(userService.getUser("123")?.name)
}
Example in Java (Setter Injection):
java
class UserRepository {
public User getUser(String id) {
return new User(id, "John Doe");
}
}
class UserService {
private UserRepository userRepository;
// Setter method for injecting dependency
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User getUser(String id) {
return userRepository.getUser(id);
}
}
public class Main {
public static void main(String[] args) {
UserRepository userRepository = new UserRepository();
UserService userService = new UserService();
// Injecting dependency using setter method
userService.setUserRepository(userRepository);
System.out.println(userService.getUser("123").getName());
}
}
Key Differences:
- Dependencies are provided via the class constructor.
- Most preferred for immutability and easy testing.
- All dependencies are required upfront when the object is created.
- Dependencies are injected directly into class fields (Kotlin properties).
- Less preferred because the dependencies are hidden from the constructor, making testing harder.
- Dependencies are set via public setter methods.
- Useful when dependencies are optional or change over time.
- It is not as widely used as constructor injection in immutable object patterns.
Conclusion:
- Constructor Injection is the most commonly recommended approach for dependency injection, as it promotes immutability, makes dependencies explicit, and improves testability.
- Field Injection and Setter Injection are more flexible but less explicit, making them more suitable when dependencies are optional or changeable during the object's lifetime.
No comments:
Post a Comment