Kotlin 위임 프로퍼티 Observable
코틀린은 프로퍼티 위임 이라는 기능을 제공한다.
프로퍼티 위임을 사용하면 일반적인 프로퍼티의 행위를 추출해서 재사용할 수 있다.
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)
}