81 lines
2.1 KiB
Markdown
81 lines
2.1 KiB
Markdown
![]() |
---
|
||
|
tags: [OOP]
|
||
|
---
|
||
|
|
||
|
# Dependency injection
|
||
|
|
||
|
Dependency injection is an object-oriented design pattern that allows for the
|
||
|
decoupling of dependencies between objects making code more maintainable,
|
||
|
testable and modular.
|
||
|
|
||
|
The basic idea is to resist having any class (C) depend on the specific
|
||
|
implementation details of any other class to which it sustains a dependency (D).
|
||
|
If we can abstract the implementation of D, this makes it easier to swap-out and
|
||
|
change C's dependencies later on without having to re-write C as a result of
|
||
|
changes to D.
|
||
|
|
||
|
This is where interfaces become very helpful because they are schematic
|
||
|
representations of the main methods in a given class (basically their names,
|
||
|
params and return value). As long as this is kept consistent in D when changes
|
||
|
are made, you avoid conflicts when changes or refactorings are made to D.
|
||
|
|
||
|
## Example
|
||
|
|
||
|
In the example to follow we will have two classes:
|
||
|
|
||
|
- `ConsoleLogger`
|
||
|
- This will simply log something and be a dependency for the class below
|
||
|
|
||
|
* `UserService`
|
||
|
- This will depend on `ConsoleLogger`
|
||
|
|
||
|
First we define an interface for the dependency:
|
||
|
|
||
|
```ts
|
||
|
interface ILogger {
|
||
|
log(message: string): void;
|
||
|
}
|
||
|
```
|
||
|
|
||
|
So this is a class that has a single method `log` which receives a `message`
|
||
|
string as an argument and returns a side-effect.
|
||
|
|
||
|
Now we'll implement this class:
|
||
|
|
||
|
```ts
|
||
|
class ConsoleLogger implements ILogger {
|
||
|
log(message: string): void {
|
||
|
console.log(message);
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Now we create a class that depends on it:
|
||
|
|
||
|
```ts
|
||
|
class UserService {
|
||
|
private logger: ILogger;
|
||
|
|
||
|
constructor(logger: ILogger) {
|
||
|
this.logger = logger;
|
||
|
}
|
||
|
|
||
|
createUser(username: string): void {
|
||
|
// ... (some logic to create a user)
|
||
|
this.logger.log(`User created: ${username}`);
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
We can see that the constructor references the `Logger` interface. Thus we
|
||
|
inject the dependency when instantiating `UserService`:
|
||
|
|
||
|
```ts
|
||
|
// First implement our earlier class that matches the `Logger` in its shape:
|
||
|
const logger: ILogger = new ConsoleLogger();
|
||
|
|
||
|
// Then pass it as a dependency:
|
||
|
const userService: UserService = new UserService(logger);
|
||
|
userService.createUser("John Doe");
|
||
|
```
|