-
21장 - 일반적인 프로퍼티 패턴은 프로퍼티 위임으로 만들어라Kotlin/Effective Kotlin 요약 2023. 3. 4. 00:01728x90
프로퍼티 위임이란?
property delegate를 사용하는 것을 프로퍼티 위임이라 부릅니다
대표적으로 지연 프로퍼티가 있습니다.
lazy 프로퍼티는 이후에 처음 사용하는 요청이 들어올 때 초기화되는 프로퍼티를 의미합니다.
다른 언어에서는 대부분 이를 복잡하게 구현해야 하지만 코틀린에서는 lazy 함수를 활용하는 프로퍼티 위임으로 간단하게 구현할 수 있습니다.
또한 변화가 있을 때 이를 감지하는 observable 패턴을 쉽게 만들 수 있습니다.
예를 들어 목록을 출력하는 리스트 어댑터가 있다면 내부 데이터가 변경될 때마다 변경된 내용을 다시 출력해야 합니다.
이때 observable 델리게이트를 기반으로 간단하게 구현할 수 있습니다.
프로퍼티가 사용될 때 로그를 출력하는 예제
기본적인 구현 방법은 게터와 세터에서 로그를 출력하는 방법이 존재합니다.
이런 경우 여러 프로퍼티에서 거의 같은 처리를 하게 됩니다.
이를 프로퍼티 위임으로 추출해낼 수 있습니다.
fun main() { var token: String? by LoggingProperty(null) token = "hi" println() val getToken = token //출력 //token changed from null to hi //token returned value hi } private class LoggingProperty<T>(var value: T){ operator fun getValue( thisRef: Any?, prop: KProperty<*>, ): T{ print("${prop.name} returned value $value") return value } operator fun setValue( thisRef: Any?, prop: KProperty<*>, newValue: T, ) { val name = prop.name print("$name changed from $value to $newValue") value = newValue } }
val의 경우에는 getValue만 구현하면 되지만 var경우에는 setValue까지 구현해주어야 합니다.
공식문서 : https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/get-value.html
KProperty란?
Kotlin에서는 리플랙션을 활용할 때 앞에 K를 붙입니다.
예를 들어 KClass처럼 활용합니다.
val x = 1 fun main() { println(::x.get()) println(::x.name) }
위의 코드에서는 :: 연산자를 사용해서 KProperty<Int>임을 나타냅니다.
다음과 같은 프로퍼티 델리케이터를 알아 두면 좋습니다.
- lazy
- Delegates.observable
- Delegates.vetoable
- Delegated.notnull
observable과 vetoable이란?
observable활용
class MyUser { var name: String by Delegates.observable("초기값") { prop, old, new -> println("$old 값이 $new 값으로 변경됩니다.") } } fun main() { val user = MyUser() user.name = "홍길동" user.name = "임꺽정" } //출력 //초기값 값이 홍길동 값으로 변경됩니다. //홍길동 값이 임꺽정 값으로 변경됩니다.
vetoable활용
class MoreBiggerInt(initValue: Int) { var value: Int by Delegates.vetoable(initValue) { property, oldValue, newValue -> { val result = newValue > oldValue if(result) { println("더 큰 값이므로 값을 변경합니다.") } else { println("작은 값이므로 변경을 취소합니다.") } result }() } } fun main() { val storeBiggerInt = MoreBiggerInt(10) storeBiggerInt.value = 20 println("${storeBiggerInt.value}") storeBiggerInt.value = 5 println("${storeBiggerInt.value}") }
vetoable 위임자를 사용한다면 특정한 조건에 따라 변경을 취소할 수 있습니다.
Delegates Object
public object Delegates { public final fun <T : kotlin.Any> notNull(): kotlin.properties.ReadWriteProperty<kotlin.Any?, T> { /* compiled code */ } public final inline fun <T> observable(initialValue: T, crossinline onChange: (kotlin.reflect.KProperty<*>, T, T) -> kotlin.Unit): kotlin.properties.ReadWriteProperty<kotlin.Any?, T> { /* compiled code */ } public final inline fun <T> vetoable(initialValue: T, crossinline onChange: (kotlin.reflect.KProperty<*>, T, T) -> kotlin.Boolean): kotlin.properties.ReadWriteProperty<kotlin.Any?, T> { /* compiled code */ } }
위에서 다루었던 observable과 vetoable이 존재합니다.
onChange에서 observable은 Unit을 반환하고 vetoable은 Boolean을 반환하기 때문에 위의 차이점이 발생합니다.
결론
굉장히 범용적으로 사용되는 프로퍼티 델리게이트들을 활용할 수 있으며 LoggingProperty 같이 델리게이터를 직접 만들어서 사용할 수도 있습니다.
728x90'Kotlin > Effective Kotlin 요약' 카테고리의 다른 글
23장 - 타입 파라미터의 섀도잉을 피하라 (0) 2023.03.06 22장 - 일반적인 알고리즘을 구현할 때 제너릭을 사용하라 (0) 2023.03.05 20장 - 일반적인 알고리즘을 반복해서 구현하지 말라 (0) 2023.02.19 19장 - knowlege를 반복하여 사용하지 말라 (0) 2023.02.18 18장 - 코딩 컨벤션을 지켜라 (0) 2023.02.17