Typescript- OOP Programing-3

0

객체지향의 원칙 중 하나인 inheritance와 polymorphism에 대해 알아보자.

3. Inhertiance

지난 oop-programing-2이어서 상속에 대해 예시와 함꼐 알아보자.

  • 지난 시간의 커피머신에 이어서 caffeelatte machine 을 만들어 준다고 해보자.

before solution - inhertiance

class caffeLatteMachine {
  private static BEANS_GRAMM_PER_SHOT: number = 23;
  private coffeeBeans: number = 0; 
  
	private constructor(coffeeBeans: number) {
      this.coffeeBeans = coffeeBeans;
    }
  
... makeMaching 메서드...
  
   makeCoffee(shots: number): CoffeeCup {
      this.grindBeans(shots);
      this.preheat();
     	const coffee = this.extract(shots);
     	return {...coffee, hasMilk:true};
    }
}

위에 코드를 보면 그전에 만든 머신에서 constructor 뿐만 아니라 makeMachine 등의 많은 부분이 중복이 되어 있다

상속을 이용하게 되면 아래처럼 간단하게 접근이 가능하다.

After solution - inhertiance

{
  type CoffeeCup = {
    shots: number;
    hasMilk: boolean;
  };

  interface CoffeeMaker {
    makeCoffee(shots: number): CoffeeCup;
  }

  class CoffeeMachine implements CoffeeMaker { //interface를 구현 할땐 implements를 사용
    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);
    }

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

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

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

  class CaffeLatteMachine extends CoffeeMachine { //extends 를 통해 다른 클래스를 상속할 수 있다.
    //자식에서 따로 constructor를 따로 구현하는 경우 super를 꼭 호출애햐한다.
    //부모 클래스에서 필요한 beans를 super를 통해 전달 해주고 그 뒤에 자식에서 사용하는 인자를 넣어서 만 들 수 있다.
    
    constructor(beans: number, public readonly serialNumber: string) { //라떼기계의 경우에는 시리얼 넘버가 따로 존재하게됨
      super(beans);
    }
    private steamMilk(): void {
      console.log("Steaming some milk... 🥛");
    }
    makeCoffee(shots: number): CoffeeCup {
      // super를 이용하면 부모 클래스의 함수를 호출하거나 접근 가능.(공통적인 기능은 제사용하면서 자식 클래스에 특화된 기능을 추가 할 수 있음)
      // 이 경우 커피를 만드는 절차를 다 거치고, 그 뒤에 우유만 데워서 넣어서 주는 경우로 아래와 같이 활용이 가능하다.
      const coffee = super.makeCoffee(shots); 
      this.steamMilk();
      return {
        ...coffee,
        hasMilk: true,
      };
    }
  }

  const machine = new CoffeeMachine(23); //상속이 없는 경우!
  const latteMachine = new CaffeLatteMachine(23, "latte123");
  const coffee = latteMachine.makeCoffee(1);
  console.log(coffee);
  console.log(latteMachine.serialNumber);
}

4. Polymorphism(다형성)

다형성을 이용하면 한 가지의 클래스나 인터페이스를 통해 다양한 방식으로 구현한 클래스를 만들 수 있다.

다형성의 장점

  • 동일한 부모 클래스를 상속했을 때, 동일한 함수를 어떤 클래스인지 구분하지 않고 공통된 메서드를 호출할 수 있는 점이라 할 수 있다. (1)
  • 내부적으로 구현된 다양한 클래스들이 한 가지의 인터페이스를 구현가능하다. (2)
{
  type CoffeeCup = {
    shots: number;
    hasMilk?: boolean;
    hasSugar?: boolean;
  };

  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);
    }

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

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

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

  class CaffeLatteMachine extends CoffeeMachine {
    constructor(beans: number, public readonly serialNumber: string) {
      super(beans);
    }
    private steamMilk(): void {
      console.log('Steaming some milk... 🥛');
    }
    makeCoffee(shots: number): CoffeeCup {
      const coffee = super.makeCoffee(shots);
      this.steamMilk();
      return {
        ...coffee,
        hasMilk: true,
      };
    }
  }

  class SweetCoffeeMaker extends CoffeeMachine {
    makeCoffee(shots: number): CoffeeCup {
      const coffee = super.makeCoffee(shots);
      return {
        ...coffee,
        hasSugar: true,
      };
    }
  }

  -------------------------
  const machines = [ // - (3)
      new CoffeeMachine(16),
	    new CaffeLatteMachine(16, '1'),
  	  new SweetCoffeeMaker(16),
  ]
    
    
    -------------------
  
  // CoffeeMaker라는 공통적인 인터페이스의 배열로 만들 수 있음.
  const machines: CoffeeMaker[] = [ // (2)
    new CoffeeMachine(16),
    new CaffeLatteMachine(16, '1'),
    new SweetCoffeeMaker(16),
    new CoffeeMachine(16),
    new CaffeLatteMachine(16, '1'),
    new SweetCoffeeMaker(16),
  ];
  machines.forEach(machine => {
    console.log('-------------------------');
    machine.makeCoffee(1); // (1) makeCoffee라는 공통된 api 메소드를 호출 할 수 있다.
    // (2)의 경우에는 한가지 makeCoffee()라는 메소드만 활용이 가능하다. 인터페이스에 규약된 함수하나만 호출이 가능하다.
    // (3)의 경우에는 clean, makecoffee, fillCoffeeBeans 메소드 등을 사용 할 수 있다.
  });
}
profile
문과생 개발자되다

0개의 댓글