여러 개의 클래스를 상속받아 사용하는 경우, 그 규모가 커질 수록 구조가 복잡해진다.
(족보가 꼬인다..라고 표현)
타입스크립트는 1개의 클래스만 상속받을 수 있다.
class sportsCar extends Car, RedCar // 불가능
Composition 과 Dependency Injection 으로 해결한다.
Composition : 필요한 것을 가져와서 조립하듯이 구조화(코딩) 하는 것
Dependency Injection : class 안에 외부 class를 주입하는 것
{
// CoffeeCup의 typing
type CoffeeCup = {
shots: number;
hasMilk?: boolean;
hasSugar?: boolean;
};
// CoffeeMachine의 Interface
interface CoffeeMaker {
makeCoffee(shots: number): CoffeeCup;
}
// 일반적인 커피머신
class CoffeeMachine implements CoffeeMaker {
private static BEANS_GRAMM_PER_SHOT: number = 7; // class level
private coffeeBeans: number = 0; // instance (object) level
constructor(coffeeBeans: number) {
this.coffeeBeans = coffeeBeans;
}
static makeMachine(coffeeBeans: number): CoffeeMachine {
return new CoffeeMachine(coffeeBeans);
}
private fillCoffeeBeans(beans: number) {
if (beans < 0) {
throw new Error("value for beans should be greater than 0");
}
this.coffeeBeans += beans;
}
private clean() {
console.log("cleaning the machine...🧼");
}
private grindBeans(shots: number) {
console.log(`grinding beans for ${shots}`);
if (this.coffeeBeans < shots * CoffeeMachine.BEANS_GRAMM_PER_SHOT) {
throw new Error("Not enough coffee beans!");
}
this.coffeeBeans -= shots * CoffeeMachine.BEANS_GRAMM_PER_SHOT;
}
private preheat(): void {
console.log("heating up... 🔥");
}
private extract(shots: number): CoffeeCup {
console.log(`Pulling ${shots} shots... ☕️`);
return {
shots,
};
}
makeCoffee(shots: number): CoffeeCup {
this.grindBeans(shots);
this.preheat();
return this.extract(shots);
}
}
// 싸구려 거품기로 우유를 거품내는 동작
class CheapMilkSteamer {
private steamMlik(): void {
console.log("우유를 데우는 중");
}
makeMilk(cup: CoffeeCup): CoffeeCup {
this.steamMlik();
return {
...cup,
hasMilk: true,
};
}
}
// 설탕을 제조하는 동작
class AutomaticSugarMixer {
private getSugar() {
console.log("설탕을 가져옵니다.");
return true;
}
addSugar(cup: CoffeeCup): CoffeeCup {
const sugar = this.getSugar();
return {
...cup,
hasSugar: sugar,
};
}
}
// CoffeeMachine를 상속받고, milkFrother를 injection하여 카페라테 기계 만들기
class CaffeLatteMachine extends CoffeeMachine {
// milkFrother를 dependency injection 한다.
constructor(beans: number, private milkFrother: CheapMilkSteamer) {
super(beans);
}
/**
* dependency injection 을 통해서, constructor 로 받아온 외부의 class를
* 이 클래스 내부에서 사용할 수 있다. extends를 하지 않고 말이다.
*/
// CoffeeMachine의 makeCoffee를 override 한다.
// override : 상속받은 것을 토대로 method를 재정의 하는 것.
makeCoffee(shots: number): CoffeeCup {
const coffee = super.makeCoffee(shots);
return this.milkFrother.makeMilk(coffee);
}
}
// CoffeeMachine를 상속받고, sugar를 injection하여 카페라테 기계 만들기
class SweetCoffeeMaker extends CoffeeMachine {
constructor(beans: number, private sugar: AutomaticSugarMixer) {
super(beans);
}
makeCoffee(shots: number): CoffeeCup {
const coffee = super.makeCoffee(shots);
return this.sugar.addSugar(coffee);
}
}
}
CoffeeMachine
을 상속받았기 때문에 CoffeeMachine는 기본적인 동작을 한다. 예를 들면, fillCoffeeBeans
, clean
, grindBeans
등.
여기에 우유를 스팀해서 넣거나
, 설탕을 넣는
레시피가 추가됐을 때 그것을 class
화 시키고 결합시킨다. 결합시키는 방법은 상속
과 Composition
이 있다. 어떤 것이 더 뛰어나다 할 수 없지만, 상속의 문제점을 Composition이 많이 해결해주는 것 같다.
아무튼 Dependency Injection을 통해 class 내부로 다른 class 들을 주입시켜주고, 그것을 Composition 한다. Dependency Injection 은 class의 constructor에 인자로 넣어, 그 클래스 안에서 (넣어진) 클래스를 사용하게끔 하는 것이다. (주관적인 해석)
이렇게 하여 결과적으로 클래스 안에서 같은 동작을 하지 않도록 (중복코드 발생이 일어나지 않도록) 할 수 있고, 마치 부품처럼 떼었다 붙였다 할 수 있다.
안녕하세요. 예상기님 항해99 발표회에서 뵈었던 이윤성입니다. 프로젝트, 깃허브 어디에도 이메일이나 연락처를 찾을 수가 없네요. 채용 제안을 보내고 싶은데 sw.yoonsung@gmail.com 으로 메일하나 보내주실 수 있을까요?