goongoguma's blog

Composition vs inheritence

ES6의 클래스에 대해 찾아보던 도중 합성 vs 상속(composition vs inheritance)이라는 주제의 글이 많이 보여 한번 찾아보게 되었다.

Animal이라는 클래스가 있고 각각의 하위 동물들은 부모 클래스인 Animal에게 extends 되어있다.

class Animal {
  constructor(name) {
    this.name = name;
  }

  attack() {
    console.log(`${this.name} attacked`);
  }

  walk() {
    console.log(`${this.name} walked`);
  }
}

class FlyingAnimal extends Animal {
  fly() {
    console.log(`${this.name} flew`);
  }
}

class SwimmingAnimal extends Animal {
  swim() {
    console.log(`${this.name} swam`);
  }
}

const bear = new Animal("bear");
bear.walk();

const eagle = new FlyingAnimal("eagle");
eagle.walk();
eagle.attack();
eagle.fly();

const shark = new SwimmingAnimal("shark");
shark.walk();

실행을 시키면 콘솔창에는 다음과 같이 메세지들이 뜬다.

bear walked
bear attacked
eagle walked
eagle attacked
eagle flew
shark walked
shark attacked
shark swam

모두 정상적으로 작동하고 있다. 문제가 없어보인다. 하지만 만약에 여기서 수영할 수 있고 날 수도있는 타입의 동물을 추가하고 싶다면 어떻게 해야할까? 맨 위의 예시 코드에서 FlyingSwimmingAnimal라는 클래스를 추가해보자.

class FlyingSwimmingAnimal extends FlyingAnimal {}

해당 클래스에서 FlyingAnimal 클래스에 extends 했다. 이제 동물은 날 수 있다. 하지만 수영할 수 있도록 또한 만들어야 하는데 이미 해당 클래스에서 FlyingAnimal 클래스에서 상속을 받고있으니 상황이 애매해졌다. FlyingSwimmingAnimal에 아래와 같이 SwimmingAnimal를 상속받게 하고 해당 클래스내에서 fly 함수를 작성하면 해결할 수 있다.

class FlyingSwimmingAnimal extends SwimmingAnimal {
  fly() {
    console.log(`${this.name} flew`);
  }
}

하지만 문제는 로직이 중복된다는 것이다.

만약 여기서 합성(composition)을 사용하면 어떻게 만들 수 있을까?

function swimmer({ name }) {
  return {
    swim: () => console.log(`${name} swam`),
  };
}

const obj = swimmer({ name: "Eagle" });
obj.swim(); // Eagle swam

객체를 반환하는 함수를 사용하여 Eagle swam이라는 메세지를 출력했다. 여기서 더 확장해보자.

function swimmer({ name }) {
  return {
    swim: () => console.log(`${name} swam`),
  };
}

function swimmingAnimalCreator(name) {
  const animal = { name };
  return {
    ...animal,
    ...swimmer(animal),
  };
}

const obj = swimmingAnimalCreator("Eagle");
obj.swim(); // Eagle swam

위에서 만든 SwimmingAnimal 클래스와 같은 일을 하는 함수를 만들었지만 차이점은 swimmer 함수를 자유롭게 사용함으로써 쉽게 동물의 특징들을 만들어 낼 수 있다는 것이다. 이제 위에서 말한 날수도 있고 수영도 할 수 있는 동물을 만들어보자

function flyer({ name }) {
  return {
    fly: () => console.log(`${name} flew`),
  };
}

function attackAndWalk({ name }) {
  return {
    attack: () => console.log(`${name} attacked`),
    walk: () => console.log(`${name} walked`)
  }
}

...


function flyingSwimmingAnimalCreator(name) {
  const animal = { name }

  return {
    ...animal,
    ...swimmer(animal),
    ...flyer(animal),
    ...attackAndWalk(animal)
  }
}

const obj = flyingSwimmingAnimalCreator('Eagle')
obj.swim()
obj.fly()
obj.attack()
obj.walk()

비행과 수영, 추가적으로 걸을 수 있고 공격 할 수 있는 동물을 쉽게 커스터마이징했다.

클래스, 상속, OOP에 대한 걱정없이 객체를 이용하여 원하는 부분들을 합성해 손쉽게 만들 수 있다는 부분이 composition의 장점인것 같다.