composition이라는 강력한 기능에 대해 알아보고 실제로 코드에 어떻게 적용 할 수 있는지에 대해 알아보자.
만약, 타입스크립트와 OOP의 개념에 익숙치 않고 자세히 알고 싶다면 이전 글을 참고하자.
Composition?
composition이란 클래스 상속을 하지 않고 여러개의 클래스간에 관계를 맺는 방식을 의미한다.
근데 상속이라는 좋은 방법이 있는데 굳이 composition을 이용해야 하는 이유가 있을까?
우선, 아래 사진처럼 상속은 interface를 implements하는 것 처럼 여러개를 할 수가 없다.
상속을 이용하게 되면 결국 클래스 그 자체로 서로 의사소통을 해야 한다.
이렇게 되면 클래스간의 관계가 매우 견고해져 재사용성이 매우 떨어지게 된다.
그렇기 때문에 Composition should be favored above inheritance 라고 말을 한다.(참고링크)
How to?
그래, composition이라는 것이 있는 건 알겠다.
근데 어떻게 사용하고 실제 코드에서는 어떻게 적용하는게 좋을까?
아래 코드를 통해 살펴보자.
아래 코드가 어떤 방식으로 구현됐는지는 이전 글을 참고하자.
코드가 약간 길어 졌지만, 차근차근 살펴보자.
{
type GymMember = {
muscles: number;
fatigability: number;
};
interface GymNewbie {
workout(hours: number): GymMember;
currentState: GymMember;
}
//2. Treadmill 인터페이스 정의
interface Treadmill {
run(
mins: number,
muscles: number,
fatigability: number,
deltaPerHour: number
): Record<keyof GymMember, number>;
}
class Gym implements GymNewbie {
protected muscles = 0;
constructor(
protected fatigability: number,
protected deltaPerHour: number,
//1. 생성자 함수 인자로 treadmill 전달
private treadmill: Treadmill
) {}
get currentState(): GymMember {
return { muscles: this.muscles, fatigability: this.fatigability };
}
private backWorkOut() {
console.log('working out back');
}
private chestWorkOut() {
console.log('working out chest');
}
private legsWorkOut() {
console.log('working out legs');
}
workout(hours: number): GymMember {
if (this.fatigability >= 100) {
console.log('Too Tired to Workout! Need to Rest!');
}
this.backWorkOut();
this.chestWorkOut();
this.legsWorkOut();
this.muscles += hours * this.deltaPerHour;
this.fatigability += hours * this.deltaPerHour;
//3. treadmill의 메소드인 run 실행
return this.treadmill.run(
hours * 60,
this.muscles,
this.fatigability,
this.deltaPerHour
);
}
}
// 유산소가 필요없는 클래스 생성 implements Treadmill
class NoRunning implements Treadmill {
run(
mins: number,
muscles: number,
fatigability: number,
deltaPerHour: number
): Record<keyof GymMember, number> {
console.log('no need to run');
return { fatigability, muscles };
}
}
// 유산소가 필요있는 클래스 생성 implements Treadmill
class Running implements Treadmill {
run(
mins: number,
muscles: number,
fatigability: number,
deltaPerHour: number
): Record<keyof GymMember, number> {
console.log('running...🏃💨');
fatigability += deltaPerHour * (mins / 60);
muscles += deltaPerHour * (mins / 60);
return { fatigability, muscles };
}
}
//running coach
const skinnyOne = new NoRunning();
const fatOne = new Running();
const mark = new Gym(10, 5, fatOne);
const gadot = new Gym(10, 5, skinnyOne);
console.log('------------------------------');
console.log(mark.currentState);
mark.workout(2);
console.log(mark.currentState);
console.log('------------------------------');
console.log(gadot.currentState);
gadot.workout(2);
console.log(gadot.currentState);
}
1. Gym이라는 클래스의 생성자의 인자로 treadmil이라는 변수를 전달해주자. 이때 해당 변수는 Treamill interface 타입이다.
2. treadmill이라는 멤버변수가 가지고 있는 run 메소드를 실행시켜주자.
3. Treadmill interface를 이용한 Running, NoRunning class를 만들어주자.
이때, run이라는 메소드는 각각 다른 로직을 가지게 된다.
4. NoRunning과 Running을 이용하여 skinnyOne, fatOne 인스턴스를 만들어 주자.
5. Gym 인스턴스를 생성할 때 fatOne과 skinnyOne을 treadmill로 넣어주자.
뚱뚱한 Mark와 skinny한 gadot이 있다.
이 둘은 똑같은 헬스장에 다닌다고 할 지라도, mark는 유산소를 해야 하지만 gadot은 유산소를 하면 안된다.
즉, 같은 Gym Class의 인스턴스로 만들되, workout 메소드를 실행 시켰을 때, mark는 유산소를 하고 gadot은 유산소를 하지 않아야 한다는 것이다.
마무리
이렇게 composition을 이용하면 클래스의 재사용성을 매우 높이면서 OOP적으로 코딩을 할 수 있다.
'TypeScript' 카테고리의 다른 글
[TypeScript] Cannot write file '....' because it would overwrite input file. (0) | 2022.05.02 |
---|---|
7. TypeScript - Generics! (0) | 2022.04.19 |
5. TypeScript - OOP의 원칙과 실제 코드 예시 (0) | 2022.04.14 |
4. TypeScript - Types (0) | 2022.04.11 |
3. TypeScript - 기본타입 (0) | 2022.04.11 |