ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 21장 - 일반적인 프로퍼티 패턴은 프로퍼티 위임으로 만들어라
    Kotlin/Effective Kotlin 요약 2023. 3. 4. 00:01
    728x90

    프로퍼티 위임이란?

    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

    댓글

Designed by Tistory.