Language/Kotlin

Kotlin 위임 프로퍼티 Observable

arisongeee 2023. 9. 5. 21:20
728x90
반응형

코틀린은 프로퍼티 위임 이라는 기능을 제공한다.

프로퍼티 위임을 사용하면 일반적인 프로퍼티의 행위를 추출해서 재사용할 수 있다.

 

Observable

데이터의 변경이 있을 때 변경을 감지하여 callback으로 원하는 처리를 할 수 있다.

Delegates.observable 함수를 보면  2개의 인자를 받으며 초기값과 데이터 변경이 있을 때 호출되는 콜백 함수 (onChange) 로 이우러져있다. 

    public inline fun <T> observable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Unit):
            ReadWriteProperty<Any?, T> =
        object : ObservableProperty<T>(initialValue) {
            override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onChange(property, oldValue, newValue)
        }

프로퍼티의 변경을 감지하고 싶다면 Delegates.ovservable에 위임해 원하는 프로퍼티의 변경을 쉽게 감지할 수 있다.

 

콜백 함수는 총 3개의 인자를 받는다. 

prop : 프로퍼티 정보 (KProperty)

old : 변경 전 값

new : 새로 설정된 값 

// 사용 예시
var items: Item by Delegates.observable(Item()) { 
        prop, old, new ->
        println("$old -> $new")
    }

 

ObservableProperty

Delegates.observable은 ObservableProperty 의 구현체이다.

public abstract class ObservableProperty<V>(initialValue: V) : ReadWriteProperty<Any?, V> {
    private var value = initialValue

    /**
     *  The callback which is called before a change to the property value is attempted.
     *  The value of the property hasn't been changed yet, when this callback is invoked.
     *  If the callback returns `true` the value of the property is being set to the new value,
     *  and if the callback returns `false` the new value is discarded and the property remains its old value.
     */
    protected open fun beforeChange(property: KProperty<*>, oldValue: V, newValue: V): Boolean = true

    /**
     * The callback which is called after the change of the property is made. The value of the property
     * has already been changed when this callback is invoked.
     */
    protected open fun afterChange(property: KProperty<*>, oldValue: V, newValue: V): Unit {}

    public override fun getValue(thisRef: Any?, property: KProperty<*>): V {
        return value
    }

    public override fun setValue(thisRef: Any?, property: KProperty<*>, value: V) {
        val oldValue = this.value
        if (!beforeChange(property, oldValue, value)) {
            return
        }
        this.value = value
        afterChange(property, oldValue, value)
    }
}

만약 더 복잡한 변경 로직의 처리가 필요하다면 해당 추상 클래스를 직접 상속받아 구현할 수 있다.

예를 들면, beforeChange 부분에서 변경을 허용하거나 거부하는 조건을 추가해줄 수 있다.

(beforeChange 가 false이면 프로퍼티 값 변경이 불가하다.)

ObservableProperty 는 추상 클래스이기 때문에 함수 override가 필요하다.

아래 코드와 같이 코틀린의 익명 객체를 사용해 객체를 만들고 위임해주면 별도의 클래스를 생성해주지 않고도 바로 객체를 정의하고 사용할 수 있다.

// 사용 예시
override var age: Int by object : ObservableProperty<Int>(0) {
        override fun beforeChange(property: KProperty<*>, oldValue: Int, newValue: Int): Boolean = newValue >= 0

        override fun afterChange(property: KProperty<*>, oldValue: Int, newValue: Int) {
            logger.info { "age changed to $newValue from $oldValue" }
        }
    }

 

더 복잡한 처리가 필요하다면 익명 객체를 사용하지 않고 객체를 생성해준 후 위임해주면 된다.

class AgeProperty(var propValue: Int) : ObservableProperty<Int>(propValue) {
    override fun beforeChange(property: KProperty<*>, oldValue: Int, newValue: Int): Boolean {
        return newValue >= 0
    }

    override fun afterChange(property: KProperty<*>, oldValue: Int, newValue: Int) {
        println("Age changed from $oldValue to $newValue")
    }
}

class User {
    var age: Int by AgeProperty(0)
}
728x90
반응형