리액티브 프로그래밍과 옵저버 패턴
리액티브 프로그래밍 패러다임은 최근 프로그래밍 패러다임 중 가장 중요한 패러다임 중 하나이다. 리액티브 프로그래밍 패러다임이 유행하기 이전에는 명령형 프로그래밍 패러다임이 유행했다. 명령형 프로그래밍 패러다임 하에서는 데이터가 변할 때 데이터가 변화에 따라 데이터가 변해야 하는 부분에 모두 적용을 시켜줘야 했다.
리액티브 프로그래밍 패러다임은 데이터가 변하는 것을 관찰해서 데이터가 변할 때 동작을 변하도록 만드는 것을 기본으로 한다. 즉, 데이터에 반응(Reactive)하도록 코드를 만드는 것이다. 이렇게 하면 데이터가 변화할 때 변화가 필요한 부분에 변화를 관찰하도록 코드를 넣어주면 데이터가 변화했을 때 데이터 변화에 대한 변경 사항이 즉시 게시된다.
옵저버 패턴은 리액티브 프로그래밍의 기초가 되는 패턴이다. Observer(관찰자)는 데이터의 변화를 관찰하며, 관찰한 변화를 필요한 곳에 알린다. 따라서 옵저버 패턴은 기본적으로 발행자와 구독자 둘로 구성된다. 발행자는 변화하는 데이터이며, 구독자는 데이터의 변화를 관찰해 필요한 동작을 수행한다.
옵저버 패턴 사용
데이터가 변화할 때 어떻게 옵저버 패턴이 동작하는 것일까? 옵저버 패턴은 데이터가 변화하면 이전 값과 변화한 값을 인자로 받는 메서드를 호출하는 방식으로 동작한다. RxJava의 Observable, Coroutines의 Flow, Kotlin에 기본으로 내장된 ObservableProperty, 등 가장 많이 사용되는 데이터들은 모두 같은 방식으로 구현된다. 직관적인 이해를 위해서 아래에서 각 옵저버 패턴이 어떻게 사용되는지 살펴보자.
RxJava의 옵저버 패턴
RxJava에서는 Observable을 통해 발행자를 생성하고 subscribe를 통해 구독자를 붙인다.
//발행자(Publisher)
val publisher : Observable<String> = Observable.create { emitter: ObservableEmitter<String> ->
emitter.onNext("hello")
emitter.onNext("kotlin")
emitter.onComplete()
}
//구독자(Subscriber)
publisher.subscribe {
println(it)
}
//출력
hello
kotlin
위의 코드에서는 publisher이 hello와 kotlin을 발행하고 subscriber은 해당 값을 구독해서 프린트를 하고 있다. 따라서 hello, kotlin이 순서대로 출력된다.
Coroutine의 옵저버 패턴
Coroutines에서는 flow를 통해 발행자를 생성하고 collect를 통해 구독자를 붙인다.
//발행자(Publisher)
val flow = flow {
emit("abc")
emit("def")
}
CoroutineScope(Dispatchers.IO).launch {
//구독자(Subscriber)
flow.collect {
println(it)
}
}
//출력
abc
def
위의 코드에서는 flow가 "abc", "def"를 발행하고 collect를 통해 붙여진 구독자는 해당 값을 출력한다.
Kotlin의 Delegates.observable
Kotlin의 Delegates.observable은 시작값(initialValue)과, 변경 시 동작(onChange)을 인자로 받는다. 값(data) 자체가 발행자이며, 변경 시 동작(onChange)을 구독자로 생각하면 된다.
var data: String by Delegates.observable(
initialValue = "",
//onChange : 구독자
onChange = { property: KProperty<*>, oldValue: String, newValue: String ->
println("Data Changed >> from ${oldValue} to ${newValue}")
}
)
위의 코드에서는 data가 발행자이며, onChange의 람다식이 구독자이다. RxJava나 Coroutine의 예시들과는 다르게 발행자와 구독자가 한 번에 붙여진다. 또한 RxJava나 Coroutine은 하나의 발행자에 여러 구독자를 나눠서 붙일 수 있지만 Delegates.observable은 하나의 구독자만을 붙일 수 있다.
만약 위의 코드에 시작값을 ""으로 설정하고, "a", "b", "c"를 순서대로 발행하면 다음과 같이 순서로 값들이 출력된다.
Data Changed >> from to a
Data Changed >> from a to b
Data Changed >> from b to c
전체 코드는 다음과 같다.
fun main() {
var data: String by Delegates.observable(
initialValue = "",
//onChange : 구독자
onChange = { property: KProperty<*>, oldValue: String, newValue: String ->
println("Data Changed >> from ${oldValue} to ${newValue}")
}
)
data = "a"
data = "b"
data = "c"
}
//출력
Data Changed >> from to a
Data Changed >> from a to b
Data Changed >> from b to c
옵저버 패턴 정리
위의 사용 예시를 통해 옵저버 패턴이 어떤 것인지 직관적으로 이해할 수 있었을 것이다. 다양한 방식을 보여준 이유는 옵저버 패턴은 용도에 따라서 다양하게 구현될 수 있다는 것을 보여주기 위함이다.
옵저버 패턴을 정리하면 다음의 말로 정리할 수 있다.
옵저버 패턴은 데이터의 변화를 관찰하기 위한 패턴이다.