TypeScript

[TypeScript]합성(Composition)

DevStory 2022. 3. 21.

합성(Composition)이란?

합성(Composition)은 상속과 달리 기본 또는 부모 클래스에서 상속하지 않고 다른 클래스의 인스턴스가 포함되어 있습니다. 예를 들어서 데스크톱을 만들기 위해 필요한 부품들을 구성 요소라고 생각할 수 있습니다.

 

다음은 상속과 구성의 차이점입니다.

 

상속

  • is-a 관계
  • "데스크톱은 전자 제품이다." 여기서 노트북은 자식 또는 서브 클래스가 되고 전자 제품은 기본 또는 부모 클래스가 됩니다.
  • 기본 또는 부모 클래스 변경 시 하위 클래스에서 원치 않은 동작을 할 수 있습니다. 그렇기 때문에 상속은 결합도가 높다고 말합니다. 

 

합성

  • has-a 관계
  • "데스크톱에는 CPU가 존재한다." 데스크톱이라는 클래스에 CPU라는 클래스의 인스턴스가 포함됩니다. 
  • 합성은 상속에 비해 높은 유연성과 낮은 결합도를 가집니다. 즉, 클래스 간 의존성이 낮습니다.

 

클래스를 설계할 때 클래스 간의 공통점을 찾아 클래스 트리(상속 구조)를 만드는 것보다 합성(개별로 구성된 클래스)을 사용하는 것이 더 좋습니다. 클래스 트리는 의존성이 높아 하나의 클래스라도 잘못되면 다시 설계해야 하는 상황이 발생합니다.


합성 예제

다음은 위에서 언급한 "데스크톱에는 CPU가 존재한다."를 클래스로 표현하는 예제입니다. 데스크톱에 CPU가 존재하므로 DesktopClass 클래스의 생성자에서 CPUClass 클래스 객체를 인스턴스 화합니다.

class CPUClass {}

class DesktopClass {
  cpu_field;
  
  constructor() {
    this.cpu_field = new CPUClass();
  }
}

const desktop = new DesktopClass();
반응형

클래스의 객체에 접근

클래스의 객체에 접근하기 위해 점 표기법을 한번 더 사용할 필요가 있습니다.

class CPUClass {
  printMessage() {
    console.log('printMessage call');
  }
}

class DesktopClass {
  cpu_field;
  
  constructor() {
    this.cpu_field = new CPUClass();
  }
}

const desktop = new DesktopClass();

desktop.cpu_field.printMessage();

위 예제는 desktop 객체에서 DesktopClass의 멤버 변수인 cpu_filed 객체에 접근합니다. 점 표기법을 한번 더 사용하여 printMessage() 함수를 호출합니다.


상속 예제와 비교

대부분의 개발자들은 상속보다 합성을 선호합니다. 상속은 클래스 간의 의존하는 계층 구조가 커질수록 취약해진다는 문제가 발생합니다.

 

계층 구조의 최상위 클래스를 수정하면 해당 클래스에 종속된 모든 클래스가 영향을 받을 수 있습니다. 최악의 경우 상속되는 모든 클래스를 수정하거나  클래스 설계를 다시 해야 합니다.

 

다음은 상속의 단점을 설명하는 예제입니다. Dinosaur 클래스에서 상속되는 세 개의 자식 클래스가 존재합니다.

class Dinosaur {
  walk() {
    console.log('Walking');
  }
  
  eat() {
    console.log('Eating');
  }
  
  cry() {
    console.log('Crying');
  }
}

class TRex extends Dinosaur {}
class Triceratops extends Dinosaur {}
class Brontosaurs extends Dinosaur {}

TRex, Triceratops, Brontosaurs 클래스는 모두 육지 공룡으로 walk()eat(), cry() 할 수 있습니다. 그러나 날개 달린 공룡인 Pterodactyl는 eat()할 수 없으며 클래스 추가 시 계층 구조를 변경해야 합니다.

 

Dinaoaur에서 상속되는 파충류, 조류, 포유류와 같은 클래스가 필요합니다. 그러면 TRex, Triceratops, Brontosaurs는 파충류에서 상속받고 Pterodactyl는 조류에서 상속받습니다.

다음은 이제 위 예제를 상속이 아닌 합성으로 표현하였으며 날개 달린 공룡인 Pterodactyl가 추가되었습니다.

class Walkable {
  walk() {
    console.log('Walking');
  }
}

class Eatable {
  eat() {
    console.log('Eating');
  }
}

class Cryable {
  cry() {
    console.log('Crying');
  }
}

class Flyable {
  fly() {
    console.log('Flying');
  }
}

class TRex {
  walkable;
  eatable;
  cryable;
  
  constructor() {
    this.walkable = new Walkable();
    this.eatable = new Eatable();
    this.cryable = new Cryable();
  }
}

class Pterodactyl {
  flyable;
  eatable;
  cryable;
  
  constructor() {
    this.flyable = new Flyable();
    this.eatable = new Eatable();
    this.cryable = new Cryable();
  }
}

조류 공룡을 추가하기 위해 Flyable 클래스를 추가하였고 조류 공룡은 Flyable, Eatable, Cryable 클래스 인스턴스를 가집니다. 육지 공룡은 날 수 없으므로 Flyable 대신 Walkable 클래스 인스턴스가 존재합니다.


정리

  • 합성은 클래스에 다른 클래스의 인스턴스가 포함되어 있습니다.
  • 모든 상속 관계는 합성 관계로 변환될 수 있습니다.
  • 재사용 가능하며 클래스 간의 의존성이 낮습니다.
반응형

댓글