상속의 문제점

With·2021년 9월 7일
0

상속의 문제점

  1. 여러 개의 클래스를 상속받아 사용하는 경우, 그 규모가 커질 수록 구조가 복잡해진다.
    (족보가 꼬인다..라고 표현)

  2. 타입스크립트는 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에 인자로 넣어, 그 클래스 안에서 (넣어진) 클래스를 사용하게끔 하는 것이다. (주관적인 해석)

이렇게 하여 결과적으로 클래스 안에서 같은 동작을 하지 않도록 (중복코드 발생이 일어나지 않도록) 할 수 있고, 마치 부품처럼 떼었다 붙였다 할 수 있다.

하지만, 아직 문제가 더 남아있다.

profile
주니어 프론트엔드 개발자 입니다.

1개의 댓글

comment-user-thumbnail
2021년 9월 8일

안녕하세요. 예상기님 항해99 발표회에서 뵈었던 이윤성입니다. 프로젝트, 깃허브 어디에도 이메일이나 연락처를 찾을 수가 없네요. 채용 제안을 보내고 싶은데 sw.yoonsung@gmail.com 으로 메일하나 보내주실 수 있을까요?

답글 달기