It is a design pattern used to achieve loose coupling, modularity, and better testability in your code. Instead of letting a class or struct create its own dependencies, you provide or inject those dependencies from the outside—usually at initialisation, through properties, or via method.
Why Use Dependency Injection?
- Loosely coupled code: Components don’t hard-code their dependencies, making the codebase more flexible and maintainable.
- Improved testability: You can inject mock or stub dependencies when unit testing, isolating the component under test.
- Reusability and readability: Dependencies are explicit, making code clearer and easier to reason about.
1. Constructor Injection
Pass the dependency as a parameter in your object’s initialiser. This ensures the object is always fully configured after creation and promotes immutability.
protocol DatabaseProtocol {
func fetch() -> [String]
}
class UserRepository {
let database: DatabaseProtocol
init(database: DatabaseProtocol) {
self.database = database
}
}
2. Property (Setter) Injection
Dependencies are assigned to a property after the object is created. This is often used for optional dependencies or when dependencies may change over the object’s lifecycle.
class UserRepository {
var database: DatabaseProtocol?
}
let repo = UserRepository()
repo.database = SQLiteDatabase()
3. Method Injection
Pass the dependency as an argument when calling a specific method. This is ideal for temporary dependencies.
class Printer {
func printDocument(using driver: PrintDriverProtocol) {
driver.print()
}
}
