개요
앞선 글에서 Delegates.observable이 어떻게 구현되었는지 살펴봤다. 이번 글에서는 이 구현을 간소화해서 필요한 부분만을 뽑아 옵저버 패턴을 구현해보고자 한다.
옵저버 패턴 직접 구현 해보기
1. Kotlin 변수의 변화를 인식하기 위한 interface 선언
Kotlin 변수의 변화를 인식하기 위해서는 operator fun getValue(thisRef: T?, property: KProperty<*>): T, operator fun setValue(thisRef: T?, property: KProperty<*>, newValue: T) 을 위임을 통해 다시 구현해야 한다.
interface PropertyChangeListener<T> {
fun onPropertyChanged(oldValue: T, newValue: T)
operator fun getValue(thisRef: T?, property: KProperty<*>): T
operator fun setValue(thisRef: T?, property: KProperty<*>, newValue: T)
}
따라서 PropertyChangeListener라는 interface를 만든 후 값이 변화했을 때 수행할 메서드인 onPropertyChanged 메서드와 getValue, setValue 메서드를 선언한다.
2. PropertyChangeListener 구현체 만들기
abstract class PropertyChangeListenerImpl<T>(initialValue: T) : PropertyChangeListener<T> {
private var value: T = initialValue
override operator fun getValue(thisRef: T?, property: KProperty<*>): T {
return value
}
override operator fun setValue(thisRef: T?, property: KProperty<*>, newValue: T) {
val tmp = value
value = newValue
onPropertyChanged(tmp, newValue)
}
}
인터페이스를 선언했으면 구현체를 만들어야 한다. onPropertyChanged는 필요한 부분에서 구현해야 하므로 abstract class로 PropertyChangeListener을 선언한 후 getValue와 setValue만 override 한다. setValue를 기존 값을 tmp에 캐싱 한 후 값이 변경된 후(value = newValue), onPropertyChanged 메서드를 수행하도록 구현한다.
이제 위 abstract class를 상속하는 클래스가 onPropertyChanged 메서드를 구현하면 값이 변화할 때마다 해당 메서드가 수행되게 된다.
3. PropertyChangeListenerImpl에 구현을 위임 하도록 하기 위한 attachObserver 메서드를 만든다.
inline fun <T> attachObserver(
initialValue: T,
crossinline onPropertyChanged: (oldValue: T, newValue: T) -> Unit
): PropertyChangeListener<T> {
return object : PropertyChangeListenerImpl<T>(initialValue) {
override fun onPropertyChanged(oldValue: T, newValue: T) {
onPropertyChanged(oldValue, newValue)
}
}
}
attachObserver 메서드는 시작 값(initialValue)과 값이 변화했을 때 실행될 메서드(onPropertyChanged)를 인자로 받는다. 인자로 받은 onPropertyChanged메서드는 PropertyChangeListenerImpl의 익명 객체(object)의 onPropertyChanged 메서드에 들어간다. 그렇게 되면 값이 변화할 때마다 onPropertyChanged 메서드가 수행된다.
자 이제 구현이 끝났다.
구현체 사용해보기
위의 구현체는 변수의 setValue와 getValue를 위임하기 위한 메서드들을 구현했으므로 by 키워드를 사용해 코드를 작성해야 한다.
fun main() {
var data: String by attachObserver(
initialValue = "",
onPropertyChanged = { oldValue: String, newValue: String ->
println("Data Changed >> from ${oldValue} to ${newValue}")
}
)
data = "a"
data = "b"
data = "c"
}
attachObserver에 data의 setValue와 getValue의 구현을 위임하도록 by attachObserver을 사용한 후, initialValue를 ""로 값이 변화했을 때 이전 값과 변화한 값을 출력하도록 코드를 작성한다. 그 후 data를 순서대로 a, b, c로 변경한다.
그러면 출력값은 다음과 같다.
Data Changed >> from to a
Data Changed >> from a to b
Data Changed >> from b to c
정리
위에서는 Kotlin의 변수의 operator fun을 override하는 방법으로 observer 패턴을 구현했지만, 옵저빙을 위한 객체를 만들고 해당 객체의 내부에서 동작을 직접 구현해주는 방식으로도 구현할 수 있다. observer 패턴의 구현 방식은 다양하다. 상황에 맞는 방식으로 구현하는 것이 좋다.