[이펙티브 자바] 를 읽었던 기억을 떠올렸을 때 '상속보다는 컴포지션을 사용하라' 라는 아이템이 상당히 인상 깊었다.
하지만 컴포지션 적용시키면서 다형성을 만족시키려면 인터페이스를 상속 받아야 하는데, 모두 구현해야 하므로
코드가 참 더러워지는 것을 확인할 수 있었다.
코틀린을 이를 기가 막히게 해결했다.
다시 상속과 컴포지션의 예를 살펴보고 이를 코틀린이 어떻게 간단히 해결할 수 있는지 살펴 보자.
상속의 문제점
- 모든 것을 가져올 수밖에 없음
- 슈퍼클래스의 상태, 메서드, 행위 등 모든 것을 가져온다.
- 계층 구조를 나타낼 때 굉장히 좋은 도구
- 그렇기에 일부분만을 사용하기에 부적합한 방법
- 이를 위해서 컴포지션을 사용한다.
- 슈퍼클래스의 상태, 메서드, 행위 등 모든 것을 가져온다.
- 캡슐화를 깨는 상속
- 내부적인 구현 방법의 변경으로 캡슐화가 깨진다.
컴포지션
슈퍼클래스를 상속 받기 보다 필요한 부분만을 취하기 위해,
프로퍼티로 취하면 된다.
이를 테면,
class CounterSet<T> : HashSet<T>() {
//....
}
// 위의 클래스처럼 슈퍼클래스를 직접 상속 받는 것이 아닌,
class CounterSet<T> {
private val innerSet = HashSet<T>()
// ...
}
// 이러한 형태로 프로퍼티로 가져와서 적절히 필요한 행위만 사용한다.
이렇게 하면 HashSet의 모든 것을 가져오기 보다는 CounterSet 객체가 필요한 부분만을 컴포지션을 통해 얻은 innerSet 프로퍼티로 활용할 수 있게 되었다.
하지만,
컴포지션을 활용하게 되면 다형성의 이점을 활용하지 못할 경우가 생긴다.
이럴 경우에는 포워딩 메서드를 통해 극복할 수 있다.
class CounterSet<T> : MutableSet<T>{
private val innerSet = HashSet<T> ()
//,..
// 하지만 MutableSet 인터페이스의 모든 추상 메서드를 innerSet을 이용하여 오버라이드 해야 한다.
// 개귀찮.. 코드 개더럽..
}
코드가 필요 이상으로 길어지므로 주석만 작성했다.
MutableSet의 추상 메서드가 얼마나 많은데, 그걸 어떻게 다 구현할까?
코드가 필요 이상으로 너무 길어진다. (자그마치 9개..)
코틀린을 이를 Fancy 하게 해결했다.
바로 위임 패턴을 통해서.
class CounterSet<T> (
private val innerSet = HashSet<T>(),
) : MutableSet<T> by innerSet {
//...
// 이제는 by 키워드로 인해 프로퍼티인 innerSet을 이용해 구현을 위임할 수 있다.
// by 키워드 하나로..!
}
참 Fancy 하다.
정리
하지만 다형성까지 필요한 경우가 거의 없을 것이다.
웬만한 경우는 컴포지션으로 해결할 일이 많을 것이다.
컴포지션을 활용하면, 코드를 이해하기 쉬우며, 유연하게 확장할 수 있다.
굳이 다형성을 가져가기 위해 위임을 사용할 필요 없이.
상속을 하지 못하게 하려면 final 키워드를 사용하면 된다.
하지만 상속하려는 클래스의 특정 메서드들의 오버라이딩을 막으려면 open 키워드를 붙이지 않으면 된다.
코틀린은 참 간단하다. (물론 이건 자바와 비슷하지만)
마지막으로 OOP 지향 방법은 상속보다는 컴포지션 사용이 best라는 점을 인지하면서 마무리한다.
'📚 Kotlin' 카테고리의 다른 글
[Effective Kotlin] 아이템 49. 하나 이상의 처리 단계를 가진 경우에는 시퀀스를 사용하라 (0) | 2023.02.04 |
---|---|
[Effective Kotlin] 아이템 47. 인라인 클래스의 사용을 고려하라 (0) | 2023.02.03 |
[Effective Kotlin] 아이템 32. 생성자 대신 팩토리 함수를 사용하라 (2) | 2023.01.29 |
[Effective kotlin] 아이템 27. 변화로부터 코드를 보호하려면 추상화를 사용하라 (0) | 2023.01.29 |
[Effective Kotlin] 아이템 24. 제네릭 타입과 variance 한정자를 활용하라 (1) | 2023.01.27 |