kotlin 위임이 궁금해 책을 따라가며 내용을 정리했다
4.1.2 open, final, abstract 변경자: 기본적으로 final
상속을 위한 설계와 문서를 갖추거나, 그럴 수 없다면 상속을 금지하라
Effective Java_조슈아 블로크
정확한 문서가 없을 경우 하위 클래스에서 기반 클래스 작성자의 의도와 다르게 메소드를 오버라이드하여 사용할 가능성이 있다
이런 상황에서 하위 클래스가 기반 클래스에 대해 가졌던 가정이 기반 클래스의 변경으로 인해 깨져버리는 경우 하위 클래스의 동작이 예기치 않게 바뀌는 Fragile base class(취약한 기반 클래스) 문제가 발생할 수도 있다
final
코틀린에서는 상속의 문제점을 인지하고 기본적으로 모든 클래스와 메소드의 변경자값을 final로 설정했다
오버라이딩이 불가능하여 클래스 확장이 어렵다
오버라이드 하는 메소드의 구현을 하위 클래스에서 못하게 하고 싶다면 final 키워드를 명시하면 된다
open class RichButton : Clickable {
final override fun click() { } // override 메소드나 프로퍼티는 기본적으로 열려있다
}
open
상속을 허용하기 위해서는 open 변경자를 클래스 앞에 붙여야 하며,
오버라이드를 허용하고 싶은 메소드, 프로퍼티 앞에도 명시해야만 오버라이드가 가능하다
abstract
abstract class Animated {
open fun stopAnimating() {
}
fun animateTwice() {
}
abstract fun animate()
}
추상 클래스라고 할지라도 메소드는 기본적으로 final이다
추상 멤버는 하위 클래스에서 오버라이드 해야만 하기 때문에 open 변경자를 명시할 필요가 없다
추상 클래스의 abstract 키워드가 붙은 추상 함수는 하위 클래스에서 반드시 구현해야 하는 메소드이다
4.3.3 클래스 위임 : by 키워드 사용
위에서 알아본 바와 같이 코틀린은 기본적으로 클래스, 멤버, 프로퍼티 모두 final이다
그러나 종종 상속을 허용하지 않는 클래스에 새로운 동작을 추가해야 하는 경우가 있다
이런 경우 데코레이터 패턴을 사용하게 된다
상속을 허용하지 않는 기존 클래스의 인터페이스를 데코레이터가 제공하게 만들고, 기존 클래스를 데코레이터 클래스 내부에 필드로 유지하는 방법이다
https://coding-factory.tistory.com/713
하지만 데코레이터 패턴은 준비 코드가 많이 필요하다
class DelegatingCollection<T> : Collection<T> {
private val innerList = arrayListOf<T>()
override val size: Int get() = innerList.size
override fun isEmpty(): Boolean = innerList.isEmpty()
override fun contains(element: T): Boolean = innerList.contains(element)
override fun iterator(): Iterator<T> = innerList.iterator()
override fun containsAll(elements: Collection<T>): Boolean =
innerList.containsAll(elements)
}
코틀린에서는 인터페이스를 구현 시 by 키워드를 통해 그 인터페이스에 대한 구현을 다른 객체에 위임 중이라는 사실을 명시할 수 있다
class DelegatingCollection<T>(
innerList: Collection<T> = ArrayList<T>()
) : Collection<T> by innerList {}
이렇게 하면 컴파일러가 by 키워드를 보고 전달 메소드를 자동으로 생성하게 되어 개발자가 모든 코드를 작성할 필요 없다
7.5 프로퍼티 접근지 로직 재활용: 위임 프로퍼티
위임 프로퍼티를 통해 프로퍼티 값을 저장하거나 초기화하거나 읽거나 변경할 때 사용하는 로직을 재활용할 수 있다
7.5.1 위임 프로퍼티 소개
class Foo {
var p: Type by Delegate()
}
코틀린에서는 by 뒤에 있는 식을 계산해서 위임 객체를 만든다
프로퍼티 p는 접근자 로직(getter, setter 로직)을 다른 객체( Delegate 클래스의 인스턴스)에게 위임한다
접근자 로직을 위임한다는 의미는 코틀린 컴파일러가 무슨 일을 하는 지 보면 알 수 있다
class Foo {
private val delegate = Delegate() // 도우미 프로퍼티
var p: Type
set(value: Type) = delegate.setValue()
get() = delegate.getValue()
}
class Delegate {
operator fun getValue() {}
operator fun setValue() {}
}
>>> val foo = Foo()
>>> val oldValue = foo.p // Delegate getValue 호출
>>> foo.p = newValue // Delegate setValue 호출
컴파일러는 생성한 숨겨진 도우미 프로퍼티를 생성하고 그 프로퍼티를 위임 객체의 인스턴스로 초기화 한다
프로퍼티 p가 위임 객체에게 작업을 위임하게 되면 컴파일러가 자동으로 위임 객체의 접근자 함수를 호출하게 된다
겉보기엔 foo.p는 일반 프로퍼티 같지만 내부적으로는 위임 프로퍼티 객체에 있는 메소드를 호출한다
7.5.2 by lazy() : 프로퍼티 초기화 지연
프로퍼티 위임을 활용하면 프로퍼티의 초기화를 지연시킬 수 있다
지연 초기화 (lazy initialization) : 프로퍼티가 실제로 사용될 때 초기화 하는 패턴
lazy (참고): 코틀린 패키지에서 제공하는 표준 함수
public fun <T> lazy(
initializer: () → T
): Lazy<T>
//사용
private val bindingController by lazy {
FilesBindingController(viewModel::onFileOpened)
}
lazy 함수는 프로퍼티에 처음 접근할 때 초기화를 위해 전달한 람다(initializer)를 사용하여 초기화를 진행한 후, 다음 접근때는 캐시된 값을 넘겨준다
기본적으로 thread-safe하며 by 키워드와 함께 사용하여 지연된 위임 프로퍼티를 만들 수 있다
이런 방식이기 때문에 생성 하는데 비용이 많이 드는 객체를 초기화할 때 사용하면 효과적이다
'Kotlin' 카테고리의 다른 글
Flow 결합 연산자 : zip, combine (0) | 2023.04.11 |
---|---|
operator fun invoke (0) | 2023.04.10 |
정렬 (0) | 2023.01.09 |
코루틴 1 (0) | 2022.05.12 |
연산자 오버로딩 (0) | 2021.10.20 |