728x90

객체지향 프로그래밍을

"잘" 해보기위해

처음부터 다시 공부중이다.

 

상속을 쓰다보면

늘 "객체지향"라는 개념이 혼동되었다.

 

내 기억속의 "객체지향"은

마치 자동차를 만드는 작업과 같았다.

 

예를들면,

스팅어가 탄생하는 과정을 한번 그려보자.

 

[상속의 개념을 적용한 예시]

a: "우리 스팅어라는 자동차를 만들거야,

아주 빠르고 스포티한 자동차지"

b: "그러면 엔진인 gv80에서 상속받고

타이어랑 엔진은 g70에서 상속받고...

뼈대는 탄탄하게 모하비에서 상속받도록 할까요?"

 

하지만, 실제로는 그렇지않다.

아주 작은 부품들이 모여서 하나의 덩어리 부품를 만들고.

그 덩어리 부품들이 모여서 하나의 자동차를 만드는 것이다.

 

이렇게 부품을 만들고

부품을 가지고 새로운 부품을 "구성" 하는것은

Composition(구성) 이라고 한다.

 

상속이 쓸모 없느것은 아니지만

먼저 어떻게 Composition을 할지 생각해보고

다음에 그러면 공통적인 무언가만 상속하는 형태로

가져가는게 좋지 않을까

하는 개발론적인 생각을 해보았다.

 

그럼, Composition(구성)의 개념을 적용하여

스팅어와 소나타를 만드는 과정을 코드로 살펴보자.

Github링크: https://github.com/jaekwangLee/oop_js_study

{
  interface Engine {
    run(): boolean;
    stop(): boolean;
    pressure: string; // 'weak' | 'strong' | 'normal';
  }

  interface Mission {
    mode: string; // auto | manual
    change(gear: number): number;
    clutch(): void;
    getGear(): number;
  }

  interface Frame {
    material: string; // Materials
    strength(): string;
    size(): { length: number; width: number; height: number };
    crash(): string;
  }

  interface Tire {
    brand: string;
    purpose: string;
    count: number;
    role(): void;
    pause(): void;
  }

  type Materials = "stainless" | "steel" | "aluminium";
  type TirePurpose = "winter" | "summer" | "for season";

  class GDI implements Engine {
    constructor(private hp: number, private torque: number) {}

    pressure = "strong";

    private heating = () => {
      console.log("heating ...♨");
    };

    private cooling = () => {
      console.log("cooling ...❄️");
    };

    run = () => {
      this.heating();
      console.log("engine is working with ", this.hp, this.torque);
      console.log("use power ", this.pressure);
      return true;
    };

    stop = () => {
      this.cooling();
      console.log("engine is stopping");
      console.log("power to zero ");
      return false;
    };
  }

  class MPI implements Engine {
    constructor(private hp: number, private torque: number) {}

    pressure = "weak";

    private heating = () => {
      console.log("heating ...♨");
    };

    private cooling = () => {
      console.log("cooling ...❄️");
    };

    run = () => {
      this.heating();
      console.log("engine working with ", this.hp, this.torque);
      console.log("use power ", this.pressure);
      return true;
    };

    stop = () => {
      this.cooling();
      console.log("engine is stopping");
      console.log("power to zero ");
      return false;
    };
  }

  class CVT implements Mission {
    mode = "";
    constructor(private gear: number, mode: "manual" | "auto") {
      this.mode = mode;
    }

    clutch = () => {
      console.log("press clutch!! ䷮");
    };

    change = (nextGear: number) => {
      if (nextGear > this.gear) return this.gear;
      if (nextGear <= 0) return this.gear;

      this.clutch();

      this.gear = nextGear;
      return this.gear;
    };

    getGear = () => {
      return this.gear;
    };
  }

  class BodyOnFrame implements Frame {
    constructor(
      private length: number,
      private width: number,
      private height: number
    ) {}

    material = "steel";

    strength = () => {
      return "hard";
    };

    size = () => {
      return {
        length: this.length,
        width: this.width,
        height: this.height,
      };
    };

    crash = () => {
      const strength = this.strength();
      switch (strength) {
        case "weak":
          return "die";
        case "hard":
          return "safe";
        default:
          return "injury";
      }
    };
  }

  class UniBodyFrame implements Frame {
    constructor(
      private length: number,
      private width: number,
      private height: number
    ) {}

    material = "aluminum";

    strength = () => {
      return "normal";
    };

    size = () => {
      return {
        length: this.length,
        width: this.width,
        height: this.height,
      };
    };

    crash = () => {
      const strength = this.strength();
      switch (strength) {
        case "weak":
          return "die";
        case "hard":
          return "safe";
        default:
          return "injury";
      }
    };
  }

  // 작업중
  class FourSeasonTire implements Tire {
    brand = "";
    purpose = "";
    count = 0;
    characters = [];

    constructor(brand: string, purpose: string, count: number) {
      this.brand = brand;
      this.purpose = purpose;
      this.count = count;
    }

    role = () => {
      console.log("rolling ...🚗");
    };

    pause = () => {
      console.log("pause with break ...🛑");
    };
  }

  class SummerTire implements Tire {
    brand = "";
    purpose = "";
    count = 0;

    constructor(
      brand: string,
      purpose: string,
      count: number,
      private characters?: Array<string>
    ) {
      this.brand = brand;
      this.purpose = purpose;
      this.count = count;
    }

    role = () => {
      console.log("rolling ...🚗");
    };

    pause = () => {
      console.log("pause with break ...🛑");
    };
  }

  class Car {
    constructor(
      private tire: Tire,
      private frame: Frame,
      private engine: Engine,
      private mission: Mission,
      private name: string
    ) {}

    turnOn = () => {
    console.log('Hi, myname is ', this.name);
      this.engine.run();
    };

    turnOff = () => {
      this.engine.stop();
    };

    start = () => {
      if (this.mission.mode === "manual" && this.mission.getGear() <= 0) {
        console.log("please, change the gear");
      } else {
          console.log("car is starting ~~ 🚗");
        this.tire.role();
      }
    };

    stop = () => {
      if (this.mission.mode === "manual" && this.mission.getGear() > 0) {
        this.mission.clutch();
        this.mission.change(0);
      }

      console.log("car is stopping ~~ 🚗");
      this.tire.pause();
    };
  }

  // make car !
  // tires
  const normalTires = new FourSeasonTire("Nexen", "four season", 4);
  const summerTires = new SummerTire("Kumho", "summer", 4);
  // engine
  const gdi = new GDI(320, 52);
  const mpi = new MPI(180, 23);
  // mission
  const cvt4 = new CVT(6, "auto");
  const cvt16 = new CVT(12, "auto");
  // frame
  const unibody = new UniBodyFrame(4950, 1900, 1720);
  const bodyon = new BodyOnFrame(4920, 1870, 1700);

  const sonata = new Car(normalTires, bodyon, mpi, cvt4, "sonata");
  const stinger = new Car(summerTires, unibody, gdi, cvt16, "stinger");

  sonata.turnOn();
  sonata.start();
  console.log('---------------')
  stinger.turnOn();
  stinger.start();
}

조금 길어보이지만 Composition의 개념을 통해

엔진/미션/타이어/뼈대로 구성된 자동차를 만드는 과정이다.

 

코드에서 알수 있듯이

각 부품멸 "설계도"에 해당하는 interface를 만들고

"설계도"를 가지고 부품(class)를 만들었다.

 

위에서는 두가지정도씩 부품을 만들었지만,

설계도를 가지고 얼마든지 다양한 부품을 찍어낼수있다.

 

그리고 각 설계도면을 가지고 만든 부품을

Car 라는 최종 부품, 산출물이 받게된다.

 

Car는

각 부품이 interface로 정의되어

아주 유연하게 설계되었다.

마치 그 유명한 "붕어빵 기계"와 같다.

 

위 코드를 실행한 결과

Hi, myname is  sonata
heating ...♨
engine working with  180 23
use power  weak
rolling ...🚗
car is starting ~~ 🚗
---------------
Hi, myname is  stinger
heating ...♨
engine is working with  320 52
use power  strong
rolling ...🚗
car is starting ~~ 🚗

이렇게 귀여운

자동차 두대의 동작을 살펴볼 수 있다.

728x90
반응형