개요
앞선 글에서 옵저버 패턴이 무엇인지, 옵저버 패턴을 어떻게 사용하는지 살펴봤다. 단순히 라이브러리의 구현체를 가져다 쓰는 것도 좋지만, 구현체가 어떻게 이루어져 있는지 살펴보아야 더욱 깊게 이해할 수 있다. 따라서 이번 글에서는 옵저버 패턴이 어떻게 구현되어 있는지 내부를 직접 살펴보고 구현해보고자 한다. 이번 글에서는 Kotlin에서 기본으로 제공되는 메서드를 살펴보고, 비슷한 메서드를 만들어보도록 하자.
Delegates.observable을 활용한 Observer Pattern 적용
Kotlin에서는 Delegates Object에서 제공되는 observable 함수를 통해 옵저버 패턴을 만들어낼 수 있다.
String 타입의 data 변수를 선언한 후 변수의 구현을 Delegates.observable로 위임하는 아래 예제를 살펴보자.
fun main() {
var data: String by Delegates.observable(
initialValue = "",
onChange = { property: KProperty<*>, oldValue: String, newValue: String ->
println("Data Changed >> from ${oldValue} to ${newValue}")
}
)
data = "a"
data = "b"
data = "c"
}
위 코드는 data라는 변수의 초기값이 "" 이고 구독자는 값의 변화를 구독해서 onChange 메서드를 수행한다. 따라서 위의 코드의 출력 값은 다음과 같다.
Data Changed >> from to a
Data Changed >> from a to b
Data Changed >> from b to c
어떻게 위의 방식이 동작하는 것일까? Delegates.observable의 내부를 살펴보자.
옵저버 패턴 내부 구현 살펴보기
Delegates.observable은 내부에서 ObservableProperty abstract class를 익명 객체로 구현해 return한다.
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)
}
이 ObservableProperty는 afterChange 메서드를 override 하며, 인자 값으로 넘겨진 onChange 람다식이 이 메서드 내부에서 수행된다. 즉, 데이터가 변경된 이후(afterChange) oldValue와 newValue를 인자로 받는 메서드가 수행되는 것이다.
Kotlin의 변수는 변수의 값을 변경하거나 변수의 값에 접근하기 위해 operator fun setValue와 operator fun getValue를 구현해야 한다. by 키워드를 통해 Delegates.observable에 data : String의 구현을 위임 했으므로 이 두 메서드를 구현해야한다. 따라서 위의 ObservableProperty에서 해당 값이 구현되어 있어야 하는 것을 추정할 수 있다.
public abstract class ObservableProperty<V>(initialValue: V) : ReadWriteProperty<Any?, V>
public interface ReadWriteProperty<in T, V> : ReadOnlyProperty<T, V> {
public override operator fun getValue(thisRef: T, property: KProperty<*>): V
public operator fun setValue(thisRef: T, property: KProperty<*>, value: V)
}
실제로 내부를 보면 ObservableProperty는 ReadWriteProperty 인터페이스를 구현하고 ReadWriteProperty는 내부에 operator fun setValue와 operator fun getValue를 메서드로 가지고 있는 것을 볼 수 있다.
자 이제 ObservableProperty 내부를 살펴보자.
public abstract class ObservableProperty<V>(initialValue: V) : ReadWriteProperty<Any?, V> {
private var value = initialValue
protected open fun beforeChange(property: KProperty<*>, oldValue: V, newValue: V): Boolean = true
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)
}
}
Observable Property 내부에는 getValue와 setValue가 구현되어 있다. 여기에는 변수가 변화하기 전 수행되는 beforeChange 메서드와 변수가 변화한 후 수행되는 afterChange 메서드가 선언되어 있다. beforeChange 메서드는 setValue에서 실제로 값이 변화하게 되는 this.value = value 전에 수행되고 afterChange는 변화한 이후에 수행되게 된다.
다시 Delegates.observable로 돌아가면 Delegates.observable은 onChange 메서드를 받아서 ObservableProperty의 afterChange에서 수행되도록 하는 것을 볼 수 있다.
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)
}
정리
Kotlin에서 제공되는 Delegates.observable은 operator fun setValue 메서드를 override해서 데이터가 변경될 때 onChange 메서드가 수행되는 방식으로 구현된다.
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)
}
이전 값은 oldValue로 캐싱해놓고 this.value = value로 값이 바뀐 후에 onChange메서드를 수행하는 afterChange 메서드를 수행한다.