Constructor Injection, Property Injection vs. Method Injection

· 2 min read
Constructor Injection, Property Injection vs. Method Injection

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.