• [EffectiveKotlin] 상속보다는 컴포지션을 사용하라
    언어/Kotlin 2022. 6. 1. 01:00
    반응형

    컴포지션이란

     객체를 프로퍼티로 갖고, 함수를 호출하는 형태로 재사용하는 것이다.

     

    컴포지션 구현

    새로운 Set을 만들어 add와 addAll만 구현해야 한다고 가정해보자.

     

    1. HashSet을 상속하지 않고, 컴포지션으로 구현하자

    class CounterSet<T> {
       private val innerSet = HashSet<T>()
       var elemenetsAdded: Int = 0
          private set
       
       fun add(element: T) {
          elementsAdded++
          innerSet.add(element)
       }
       
       fun addAll(elements: Collection<T>) {
          elementsAdded += elementes.size
          innerSet.addAll(elements)
       }
    }

     위 코드는 컴포지션을 이용해 HashSet의 add와 addAll을 구현했다. 하지만 CounterSet은 더 이상 Set으로 취급되지 않는다.

     

    2. 위임패턴을 이용하자

     위임패턴은 CounterSet에 MutableSet 인터페이스를 상속한다. 컴포지션만 사용했을 때 Set으로 취급되지 않는 단점을 보완할 수 있다.

    class CounterSet<T>: MutableSet<T> {
       private val innerSet = HashSet<T>()
       var elemenetsAdded: Int = 0
          private set
       
       override fun add(element: T) {
          elementsAdded++
          innerSet.add(element)
       }
       
       override fun addAll(elements: Collection<T>) {
          elementsAdded += elementes.size
          innerSet.addAll(elements)
       }
       
       override fun contains(element: T): Boolean = innerSet.contains.containsAll(elements)
       
       override fun containsAll(element: Collection<T>): Boolean = innerSet.containsAll(elements)
       
       override fun ...
       
       override fun ...
       
       override fun ...
       .
       .
       .
    }

    단점으로 add와 addAll 뿐아니라 인터페이스의 모든 함수를 구현해야 한다. 구현된 함수를 포워딩 메서드라고 한다.

     

    3. Kotlin의 위임패턴 문법을 이용하자.

    kotlin의 위임패턴 문법을 사용하면, 컴파일 시점에 포워딩 메서드를 자동으로 만들어준다.

    class CounterSet<T>(
       private val innerSet<T> = mutableSetOf()
    ): MutableSet<T> by innerSet {
       var elemenetsAdded: Int = 0
          private set
          
       override fun add(element: T) {
          elementsAdded++
          innerSet.add(element)
       }
       
       override fun addAll(elements: Collection<T>) {
          elementsAdded += elementes.size
          innerSet.addAll(elements)
       }
    }

    4.  상속보다는 컴포지션을 사용하는게 낫지만, 아닌경우도 있다.

    - 상속은 슈퍼클래스의 모든 단위 테스트를 서브클래스로도 통과할 수 있어야 할때 사용하는 것이 좋다. 즉, B가 A의 자식일 때, A타입을 사용하는 부분에서 B로 치환해도 문제없이 동작한다. (리스코프 치환의 법칙)

    반응형

    댓글

Designed by Tistory.