컴포지션

Tags
DI
Composition
부가 설명
- 상속보다 Composition을 선호해라! - 클래스 컴포지션 보다 인터페이스 컴포지션을 선호해라!
비고
Favor COMPOSITION over inheritance.
상속보다 컴포지션을 선호하라!
💡
그렇다고 상속이 무조건 나쁘고 컴포지션이 무조건 좋은 것은 아니다. 다만 너무 심한 수직 상속관계는 피하는 것이 좋다.
 

컴포지션?

composition : 구성
단어에 의미가 핵심이다.
상속을 하지말고 구성요소로 집어넣으라는 뜻.
 

언제 쓸까?

이미 상속받은 상태인데, 만들다보니 또 중복되는 기능이 있어서 빼내려고 한다.
하지만 기존의 클래스에 추가하기에는 알맞지 않는 기능이라고 판단되어 별도의 클래스를 작성하였다.
이런 경우에 어떻게 가져다 쓸 것인가?
다중상속은 불가능하고, 인터페이스로 뽑으면 매번 내용을 정의해야 하므로 알맞지 않다.
 
  • 첫번째로 할 수 있는 접근은 수직 상속구조로 만드는 것이다.
    • A extends B
    • B extends C
    • C extends D
  • 두번째로 할 수 있는 선택이 컴포지션이다.
 

컴포지션 장점

  • 복잡한 수직 상속구조를 피할 수 있게 된다!
    • 상속의 레벨을 1~2단계로만 유지하면서 코드를 재사용 할 수 있음.
 

컴포지션 단점

  • 클래스 컴포지션을 쓰면 클래스간에 너무 타이트하게 커플링 됨.
  • 필요한 동일한 이름의 함수는 가지고 있지만 동작이 조금 다른 다른 클래스의 객체를 주입받고 싶다면 클래스를 수정해야함.
 

클래스 컴포지션 개선점

  • 클래스 끼리 커플링 시켜서 쓰지 말고, 인터페이스로 빼내서 디커플링 시켜서 사용하자.
  • 필요한 함수만 가지고 있다면 다 수용할 수 있으므로, 다른 클래스의 객체를 주입 받더라도 클래스의 코드는 변경할 필요가 없다.
 

컴포지션 예시

클래스 컴포지션 코드 ( 클래스끼리 타이트하게 커플링 된 경우 )
  • 커피머신 클래스는 종류와 상관없이 거품내기()가 가능한 거품기와, 설탕제조()가 가능한 설탕제조기가 필요할 뿐임.
  • 컴포지션 구현 시 구체적인 클래스명을 지정하였으므로, 싸구려거품기싸구려설탕제조기 외에 다른 거품기와 설탕제조기를 쓰고싶다면 커피머신 클래스의 코드를 수정해야함.
class 싸구려거품기 { 거품내기(): void { console.log("오이외옹잉 싸구려 거품을 낸다 ... "); } } class 싸구려설탕제조기 { 설탕제조(): void { console.log("퐢오팍팍 싸구려 설탕을 만든다 ... "); } } class 커피머신 { constructor( private 거품기: 싸구려거품기, // 컴포지션, 다른 클래스의 객체를 주입받음. private 설탕제조기: 싸구려설탕제조기 // 컴포지션, 다른 클래스의 객체를 주입받음. ) {} 커피내리기() { console.log("커피를 내리고 있습니다 ... "); } 기본커피() { this.커피내리기(); this.거품기.거품내기(); this.설탕제조기.설탕제조(); console.log("커피가 완성되었습니다."); } } const myCoffeeMachine = new 커피머신( new 싸구려거품기(), // DI(Dependency Injection) new 싸구려설탕제조기() // DI(Dependency Injection) ); myCoffeeMachine.기본커피();
notion image
 
인터페이스로 디커플링 시킨 코드
  • 컴포지션할 객체의 구체적인 클래스명을 명시하지 않고 인터페이스로 타입을 지정한다.
  • 해당 인터페이스를 구현하는 객체이기만 하면 되므로, 커피머신 클래스의 코드를 수정하지 않고도 다른 클래스의 객체를 주입받을 수 있게 되었다.
interface 거품제조기 { 거품내기(): void; } interface 설탕제조기 { 설탕제조(): void; } class 싸구려거품기 implements 거품제조기 { 거품내기(): void { console.log("오이외옹잉 싸구려 거품을 낸다 ... "); } } class 고급거품기 implements 거품제조기 { 거품내기(): void { console.log("고오오오급 거품을 낸다 ... "); } } class 싸구려설탕제조기 implements 설탕제조기 { 설탕제조(): void { console.log("퐢오팍팍 싸구려 설탕을 만든다 ... "); } } class 고급설탕제조기 implements 설탕제조기 { 설탕제조(): void { console.log("고오오오급 설탕을 만든다 ... "); } } class 커피머신 { constructor( private 거품기: 거품제조기, // 컴포지션, 거품제조기 인터페이스를 구현한 객체이기만 하면 됨. private 설탕제조기: 설탕제조기 // 컴포지션, 설탕제조기 인터페이스를 구현한 객체이기만 하면 됨. ) {} 커피내리기() { console.log("커피를 내리고 있습니다 ... "); } 기본커피() { this.커피내리기(); this.거품기.거품내기(); this.설탕제조기.설탕제조(); console.log("커피가 완성되었습니다."); } } const myCoffeeMachine1 = new 커피머신( new 싸구려거품기(), // DI(Dependency Injection) new 싸구려설탕제조기() // DI(Dependency Injection) ); const myCoffeeMachine2 = new 커피머신( new 고급거품기(), // DI(Dependency Injection) new 고급설탕제조기() // DI(Dependency Injection) ); myCoffeeMachine1.기본커피(); myCoffeeMachine2.기본커피();
notion image