의존성 역전의 원칙이란?
의존성 역전의 원칙(Dependency Inversion Principle, DIP)은 객체 지향 설계 원칙 중 하나로, 고수준 모듈이 저수준 모듈에 의존하는 것이 아니라 둘 다 추상화에 의존하도록 해야 한다는 원칙이다.
1. 고수준 모듈과 저수준 모듈이란?
- 고수준 모듈이란: 모듈의 본질적 기능과 책임을 말하며, 구체적 동작은 포함되지 않는다. (무엇을?)
- 저수준 모듈이란: 모듈 시스템의 구체적인 세부 사항과 기능을 나타낸다. (어떻게?)
그림에서는 HL1은 ML1보다 고수준의 모듈이다. 이때 일반적인 제어의 흐름으로는 고수준의 모듈이 저수준의 모듈에 의존하게 된다.
2. 의존이란?
의존이란 어떠한 모듈, 클래스 또는 컴포넌트가 다른 모듈, 클래스 또는 컴포넌트의 기능이나 행위에 종속되어 있음을 이야기 한다. 즉 고수준 모듈인 HL1은 저수준 모듈인 ML1의 특정한 기능이나 동작 없이는 제대로 작동할 수 없다.
3. 역전되지 않은 의존성의 문제점
이렇게 의존성이 역전되지 않은 상황에서는 코드의 '유연성'이 떨어지게 된다. 코드로 예를 들자면 다음과 같다.
// 저수준 모듈
class Tire {
roll() {
console.log("Tire rolling...");
}
}
// 고수준 모듈
class Car {
private frontLeftTire: Tire;
private frontRightTire: Tire;
private rearLeftTire: Tire;
private rearRightTire: Tire;
constructor() {
this.frontLeftTire = new Tire();
this.frontRightTire = new Tire();
this.rearLeftTire = new Tire();
this.rearRightTire = new Tire();
}
drive() {
console.log("Car driving...");
this.frontLeftTire.roll();
this.frontRightTire.roll();
this.rearLeftTire.roll();
this.rearRightTire.roll();
}
}
const car = new Car();
car.drive();
위와 같은 코드에서는 Tire의 종류, 예를 들어 겨울용 타이어로 바꾸게 된다면 코드를 여러번 수정해야 하고 유연성이 떨어지는 결과를 초래한다.
4. 의존성 역전
Object Oriented의 특성 중 다형성을 이용하여 이러한 문제를 해결할 수 있다.
상위 모듈과 하위 모듈 모두 인터페이스(I)에 의존하게 만들면 하위 모듈의 구현 세부 사항에 상위 모듈을 의존하지 않게 할 수 있다. 구체적 예시는 아래와 같다.
// 인터페이스 (추상화)
interface ITire {
roll(): void;
}
// 저수준 모듈
class SummerTire implements ITire {
roll() {
console.log("Summer tire rolling...");
}
}
class WinterTire implements ITire {
roll() {
console.log("Winter tire rolling...");
}
}
// 고수준 모듈
class Car {
private frontLeftTire: ITire;
private frontRightTire: ITire;
private rearLeftTire: ITire;
private rearRightTire: ITire;
constructor(frontLeft: ITire, frontRight: ITire, rearLeft: ITire, rearRight: ITire) {
this.frontLeftTire = frontLeft;
this.frontRightTire = frontRight;
this.rearLeftTire = rearLeft;
this.rearRightTire = rearRight;
}
drive() {
console.log("Car driving...");
this.frontLeftTire.roll();
this.frontRightTire.roll();
this.rearLeftTire.roll();
this.rearRightTire.roll();
}
}
const summerCar = new Car(new SummerTire(), new SummerTire(), new SummerTire(), new SummerTire());
summerCar.drive();
const winterCar = new Car(new WinterTire(), new WinterTire(), new WinterTire(), new WinterTire());
winterCar.drive();
이렇게 코드를 수정하면 상위 모듈인 Car는 구체적인 타이어의 구현에 의존하지 않고 인터페이스 ITire에만 의존하게 되어 차후 다양한 종류의 타이어를 추가, 변경하여도 Car 클래스를 수정하지 않아도 된다.
Nest.js에서의 의존성 역전
현재 가장 익숙한 프레임워크인 Nest.js로 예시를 들어 설명한다.
1. 인터페이스 정의
// students.interface.ts
export interface IStudentService {
findAll(): Promise<Student[]>;
findOne(id: string): Promise<Student | null>;
create(student: CreateStudentDto): Promise<Student>;
...
}
2. 인터페이스 구현
// students.service.ts
import { Injectable } from '@nestjs/common';
import { IStudentService } from './students.interface';
@Injectable()
export class StudentService implements IStudentService {
findAll(): Promise<Student[]> {
...
}
findOne(id: string): Promise<Student | null> {
...
}
create(student: CreateStudentDto): Promise<Student> {
...
}
...
}
3. 컨트롤러에서 서비스 사용
// students.controller.ts
import { Controller, Get } from '@nestjs/common';
import { IStudentService } from './students.interface';
@Controller('students')
export class StudentsController {
constructor(private readonly studentService: IStudentService) {}
@Get()
findAll(): Promise<Student[]> {
return this.studentService.findAll();
}
...
}
4. 모듈에서 구체적 구현 인터페이스 변경
ex) service를 구체화한 DatabaseStudentService라는 이름의 구현체를 사용할 경우.
@Module({
controllers: [StudentsController],
providers: [
{
provide: IStudentService, // 이 인터페이스를 사용하는 곳에.
useClass: DatabaseStudentService, // DatabaseStudentService를 제공.
},
],
})
ex) service를 구체화한 MockStudentService라는 이름의 구현체를 사용 경우.
@Module({
controllers: [StudentsController],
providers: [
{
provide: IStudentService,
useClass: MockStudentService,
},
],
})
DIP의 개념을 이용하여 의존성을 역전시키고 비지니스의 요구에 따라 구체적 서비스의 내용을 쉽게 바꿀 수 있게 되었다.
'Computer science > Architecture' 카테고리의 다른 글
[Jest] CRUD 단위 테스트 (2) | 2023.02.04 |
---|---|
[Jest] passport 로그인 단위 테스트 (0) | 2023.02.04 |
[Jest] 유저 등록 단위 테스트 (0) | 2023.02.03 |