"Giving an object its instance variables" is a quote by James Shore to summarize DI.
Using DI, an object or function receives other object or functions it depends on instead of creating them internally. It leads to loosely coupled structure.
Example
Without DI:
class UserServiceWithoutDI {
private logger: Logger;
constructor() {
this.logger = new Logger();
}
createUser(name: string): void {
this.logger.log(`User created: ${name}`);
}
}
// Usage
const userServiceWithoutDI = new UserServiceWithoutDI();
userServiceWithoutDI.createUser("Bob");
Using DI, loggerinstance is created outside and then passed:
class Logger {
log(message: string): void {
console.log(`Log: ${message}`);
}
}
class UserService {
constructor(private logger: Logger) {}
createUser(name: string): void {
this.logger.log(`User created: ${name}`);
}
}
const logger = new Logger();
const userService = new UserService(logger);
userService.createUser("Alice");
Why to use DI?
- Testing is now way easier, as we can inject a mock instead of altering the UserService class with a mock
- One can move the implementation details to the call-site. E. g. the Database Config Params are now in the entrypoint, e. g. the index.ts which reads from the ENV. The db.ts only receives the injected details
- Implementation details, e. g. the logger itself can be changed without the class
UserServicemust be changed. If we switched the logger, and now need parameters forverbose: falsewe just inject this detail. Without DI, we must alter theUserServiceitself - All imports for external dependencies can be run in the call-site. Especially in frontends, this can enable better lazy loading
DI and SOLID
Dependency Injection is not a part of SOLID. Yet, Dependency Inversion is a part, and Dependency Injection is a way of Dependency Inversion.