Kotlin/Design Patterns

[Kotlin] 옵저버 패턴 직접 구현해보기

반응형

개요

앞선 글에서 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 패턴의 구현 방식은 다양하다. 상황에 맞는 방식으로 구현하는 것이 좋다.

반응형

 

이 글의 저작권은 Kotlin World 에 있습니다. 글, 이미지 무단 재배포 및 변경을 금지합니다.