Dependency Injection & Dependency Inversion Principle
Dependency injection is a design pattern where the dependencies of a class are provided from the outside rather than being created internally. This makes the class more modular, testable, and easier to maintain.
Let's see below example:
class UserRepository {
func getUser() -> String {
return "Rishabh"
}
}
class UserService {
let userRepository = UserRepository()
func greetUser() {
userRepository.getUser()
}
}
// Usage
let userService = UserService()
userService.greetUser() // Output: Hello, Rishabh!
We can see that UserService class is creating an instance of UserRepository. This also means that UserService class is tightly coupled with UserRepository.
This violated the SOLID principle and restricted us from using the same for creating the test cases.
Here are two ways to remove this tight coupling.
Pass an instance of UserRepository in the init method of UserService.
class UserRepository {
func getUser() -> String {
return "Rishabh"
}
}
class UserService {
let userRepository: UserRepository
init(userRepository: UserRepository) {
self.userRepository = userRepository
}
func greetUser() {
userRepository.getUser()
}
}
// Usage
let userRepository = UserRepository()
let userService = UserService(userRepository: userRepository)
userService.greetUser() // Output: Hello, Rishabh!
Here, We have resolved the dependency injection principle but this violates the Dependency Inversion Principle (DIP) of the SOLID principles.
The Dependency Inversion Principle states that high-level modules should not depend on low-level modules. Both should depend on abstractions. Additionally, abstractions should not depend on details. Details should depend on abstractions.
The UserService class depends directly on the UserRepository class, which is a concrete implementation. To adhere to the Dependency Inversion Principle, UserService should depends on an abstraction (an interface or protocol) rather than a concrete class.
Here's how we can refactor the code to comply with the Dependency Inversion Principle:
// Define protocol
protocol UserRepository {
func getUser() -> String
}
// Conclere class that confirm to UserRepository protocol
class ConcreteUserRepository:UserRepository {
func getUser() -> String {
return "Rishabh "
}
}
// class that accept UserRepository in init & do the method class
class UserService {
let userRepository: UserRepository
init(userRepository: UserRepository) {
self.userRepository = userRepository
}
func greetUser() {
userRepository.getUser()
}
}
// Usage
let userRepository = ConcreteUserRepository()
let userService = UserService(userRepository: userRepository)
userService.greetUser() // Output: Hello, Rishabh yadav1!
As we can see we should be careful to solve any problem and look for all potential problems with the code. Initially, We solved the dependency injection problem but introduced a violation of DIP.
Enjoy a sunny Monday.
Sr iOS developer | Swift | Swift UI | Objective C | Flutter
4 个月Very well explained and explained in a easy and understandable way... Thanks for the post..
Tech Lead .NET, C# | Software Engineer at Societe Generale Securities Services - SGSS
10 个月Such a nice article ?????? Dependency injection need to be well understood in order to be comfortable with others design pattern