본문 바로가기
  • 살짝 구운 김 유나
Web/TypeScript

[TypeScript] Composition

by yunae 2023. 2. 25.

상속의 문제점 ?

=> 여러개의 클래스를 상속받아 사용하는 경우, 그 규모가 커질 수록 클래스간의 관계가 복잡해질 수 있음

순양은 이제 장자상속 없다

 

 

Composition

: 각각의 기능별로 클래스를 분리해 만들어 둠으로써 필요할때마다 가져다가 쓰는 것

아래의 예제에서는 우유를 추가하는 클래스, 설탕을 추가하는 클래스를 따로 분리해보자

{
  /**
   * Composition
   */

  type CoffeeCup = {
    shots: number;
    hasMilk?: boolean; // Optional
    hasSugar?: boolean; // Optional
  };

  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 level

    // 인스턴스를 만들 때 호출되는 함수
    public constructor(coffeeBeans: number) {
      this.coffeeBeans = coffeeBeans; // 커피 콩 충전
    }

    // 클래스 내부의 어떤 속성도 팔요로 하지 않기 떄문에  static으로 함수 선언
    static makeMachine(coffeeBeans: number): CoffeeMachine {
      return new CoffeeMachine(coffeeBeans);
    }

    fillCoffeeBeans(beans: number) {
      if (beans < 0) {
        throw new Error("value for beans should be greater than 0");
      }
      this.coffeeBeans += beans;
    }

    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,
        hasMilk: false,
      };
    }

    clean() {
      console.log("Cleaning the Machine");
    }

    makeCoffee(shots: number): CoffeeCup {
      this.grindBeans(shots);
      this.preheat();
      return this.extract(shots);
    }
  }

  // 우유를 넣어주는 클래스
  class CheapMilkSteamer {
    private steamMilk(): void {
      console.log("Steamin some milk...");
    }
    makeMilk(cup: CoffeeCup): CoffeeCup {
      this.steamMilk();
      return {
        ...cup,
        hasMilk: true,
      };
    }
  }

  // 설탕 제조기
  class AutomaticSugarMixer {
    private getSugar() {
      console.log("Getting some sugar from candy..");
      return true;
    }

    addSugar(cup: CoffeeCup): CoffeeCup {
      const sugar = this.getSugar();
      return {
        ...cup,
        hasSugar: true,
      };
    }
  }

  // CoffeeMachine을 상속하는 클래스
  class CaffeLatteMachine extends CoffeeMachine {
    constructor(
      beans: number,
      public readonly serialNumber: string,
      private milkFrother: CheapMilkSteamer
    ) {
      super(beans);
    }

    makeCoffee(shots: number): CoffeeCup {
      const coffee = super.makeCoffee(shots);
      return this.milkFrother.makeMilk(coffee);
    }
  }

  // CoffeeMachine을 상속하는 클래스
  class SweetCoffeeMaker extends CoffeeMachine {
    constructor(private beans: number, private sugar: AutomaticSugarMixer) {
      super(beans);
    }
    makeCoffee(shots: number): CoffeeCup {
      const coffee = super.makeCoffee(shots);
      return this.sugar.addSugar(coffee);
    }
  }

  class SweetCaffeLatteMachine extends CoffeeMachine {
    constructor(
      private beans: number,
      // 필요한 기능을 가져와서 사용
      private sugar: AutomaticSugarMixer,
      private milkFrother: CheapMilkSteamer
    ) {
      super(beans);
    }
    makeCoffee(shots: number): CoffeeCup {
      const coffee = super.makeCoffee(shots);
      const milkAdded = this.milkFrother.makeMilk(coffee);
      return this.sugar.addSugar(milkAdded);
    }
  }
}

위의 경우에 가져다 쓰는 클래스와의 의존성이 높은 문제가 발생한다.

특정 기능을 하는 클래스가 변경되었을 때, 그 클래스를 가져다 쓰는 클래스들 또한 업데이트가 되어야함!

 

 

 

더 유연하고 확장이 가능하게 하려면?

인터페이스로 분리해보자

=> 원하는 Steamer, 원하는 Sugar 클래스를 부품처럼 가져다가 조립하면서 쓸 수 있음...!

{
  /**
   * 강력한 Interface
   */

  type CoffeeCup = {
    shots: number;
    hasMilk?: boolean; // Optional
    hasSugar?: boolean; // Optional
  };

  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 level

    // 인스턴스를 만들 때 호출되는 함수
    public constructor(
      coffeeBeans: number,
      private milk: MilkFrother,
      private sugar: SugarProvider
    ) {
      this.coffeeBeans = coffeeBeans; // 커피 콩 충전
    }

    fillCoffeeBeans(beans: number) {
      if (beans < 0) {
        throw new Error("value for beans should be greater than 0");
      }
      this.coffeeBeans += beans;
    }

    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,
        hasMilk: false,
      };
    }

    clean() {
      console.log("Cleaning the Machine");
    }

    makeCoffee(shots: number): CoffeeCup {
      this.grindBeans(shots);
      this.preheat();
      const coffee = this.extract(shots);
      const sugarAdded = this.sugar.addSugar(coffee);
      return this.milk.makeMilk(sugarAdded);
    }
  }

  interface MilkFrother {
    makeMilk(cup: CoffeeCup): CoffeeCup;
  }

  interface SugarProvider {
    addSugar(cup: CoffeeCup): CoffeeCup;
  }

  // 우유를 넣어주는 클래스
  class CheapMilkSteamer implements MilkFrother {
    private steamMilk(): void {
      console.log("Steaming some milk...");
    }
    makeMilk(cup: CoffeeCup): CoffeeCup {
      this.steamMilk();
      return {
        ...cup,
        hasMilk: true,
      };
    }
  }

  class FancyMilkSteamer implements MilkFrother {
    private steamMilk(): void {
      console.log("Fancy Steaming some milk...");
    }
    makeMilk(cup: CoffeeCup): CoffeeCup {
      this.steamMilk();
      return {
        ...cup,
        hasMilk: true,
      };
    }
  }

  class ColdMilkSteamer implements MilkFrother {
    private steamMilk(): void {
      console.log("Cold Steaming some milk...");
    }
    makeMilk(cup: CoffeeCup): CoffeeCup {
      this.steamMilk();
      return {
        ...cup,
        hasMilk: true,
      };
    }
  }

  class NoMilk implements MilkFrother {
    makeMilk(cup: CoffeeCup): CoffeeCup {
      return cup;
    }
  }

  // 설탕 제조기
  class CandySugarMixer implements SugarProvider {
    private getSugar() {
      console.log("Getting some sugar from candy..");
      return true;
    }

    addSugar(cup: CoffeeCup): CoffeeCup {
      const sugar = this.getSugar();
      return {
        ...cup,
        hasSugar: true,
      };
    }
  }

  class SugarMixer implements SugarProvider {
    private getSugar() {
      console.log("Getting some sugar from jar!!..");
      return true;
    }

    addSugar(cup: CoffeeCup): CoffeeCup {
      const sugar = this.getSugar();
      return {
        ...cup,
        hasSugar: true,
      };
    }
  }

  class NoSugar implements SugarProvider {
    addSugar(cup: CoffeeCup): CoffeeCup {
      return cup;
    }
  }

  // Milk
  const cheapMilkMaker = new CheapMilkSteamer();
  const fancyMilkMaker = new FancyMilkSteamer();
  const coldMilkMaker = new ColdMilkSteamer();
  const noMilk = new NoMilk();

  // Sugar
  const candySugar = new CandySugarMixer();
  const sugar = new SugarMixer();
  const noSugar = new NoSugar();

  const sweetCandyMachine = new CoffeeMachine(12, noMilk, candySugar);
  const sweetMachine = new CoffeeMachine(12, noMilk, sugar);
  const cheapLatteMachine = new CoffeeMachine(12, cheapMilkMaker, noSugar);
  const FancyLatteMachine = new CoffeeMachine(12, fancyMilkMaker, noSugar);
  const coldFancyLatteMachine = new CoffeeMachine(12, coldMilkMaker, noSugar);
  const sweetLatteMachine = new CoffeeMachine(12, cheapMilkMaker, sugar);

}

 

 

 

자리 잠깐 비운 틈에 순양가 침투 뭔데,,,,, 아직 누군지 못찾았다,,,

사실 드립 내 취향임 ㅎ 누군데 당신 ㅎ

'Web > TypeScript' 카테고리의 다른 글

[TypeScript] Utility Type  (0) 2023.03.05
[TypeScript] 제네릭 (Generics)  (0) 2023.03.01
[TypeScript] 객체지향 (OOP)  (0) 2023.02.24
[TypeScript] 기본 타입 #2  (0) 2023.02.22
[TypeScript] 기본 타입 #1  (0) 2023.02.21

댓글