Observer Pattern adalah pola behavioral di mana satu subject mempertahankan daftar observer dan memberi tahu mereka setiap kali state berubah. Pola ini adalah fondasi pemrograman reaktif: Flow dan StateFlow di kotlinx.coroutines adalah evolusi modern dari pola ini. Pada chapter ini kita akan membangun tiga implementasi observer: classic Subject + Observer interface, function-based subscriber yang idiomatic Kotlin, dan StateFlow-like sederhana yang mensimulasikan perilaku kotlinx.coroutines.flow.StateFlow.
Tiga Varian Observer #
| Varian | Karakteristik | Cocok untuk |
|---|---|---|
| Subject + Observer interface | Klasik GoF, observer sebagai object | Observer kompleks dengan banyak method |
| Function-based subscriber | Subscriber sebagai lambda | Listener sederhana (klik tombol, log) |
| StateFlow-like | State terakhir disimpan, subscriber baru langsung dapat nilai saat ini | UI state, state management Android |
Pilihan praktis: untuk UI listener sederhana, function-based subscriber cukup. Untuk state management yang konsisten lintas subscriber, StateFlow dari kotlinx.coroutines adalah pilihan terbaik karena mendukung backpressure dan structured concurrency.
Implementasi Tiga Varian #
// Demo Observer Pattern: Publisher + Subscriber; StateFlow-like simple impl.
// === Classic Observer: Subject + Observer interface ===
interface Observer<T> {
fun update(data: T)
}
class Subject<T> {
private val observers = mutableListOf<Observer<T>>()
private var state: T? = null
fun attach(o: Observer<T>) {
observers.add(o)
}
fun detach(o: Observer<T>) {
observers.remove(o)
}
fun setState(newState: T) {
state = newState
notifyObservers()
}
private fun notifyObservers() {
val s = state ?: return
observers.forEach { it.update(s) }
}
}
// === Idiomatic Kotlin: function-based observer ===
class Observable<T>(initial: T) {
private var current = initial
private val listeners = mutableListOf<(T) -> Unit>()
var value: T
get() = current
set(newValue) {
if (newValue != current) {
current = newValue
listeners.forEach { it(current) }
}
}
fun subscribe(listener: (T) -> Unit) {
listeners.add(listener)
listener(current) // emit current value immediately
}
}
// === StateFlow-like (simplified) ===
class StateFlow<T>(initial: T) {
private var value_ = initial
private val subscribers = mutableListOf<(T) -> Unit>()
val value: T get() = value_
fun emit(newValue: T) {
value_ = newValue
subscribers.forEach { it(newValue) }
}
fun collect(collector: (T) -> Unit) {
subscribers.add(collector)
collector(value_) // immediate emit
}
}
// === Concrete observer: logger + threshold ===
class StokLogger : Observer<Int> {
override fun update(data: Int) {
println("[logger] Stok sekarang: $data")
}
}
class StokAlerm(private val threshold: Int) : Observer<Int> {
override fun update(data: Int) {
if (data < threshold) {
println("[alert] Stok di bawah $threshold: $data")
}
}
}
fun main() {
println("=== Demo Observer Pattern ===")
println("--- 1. Classic Observer (Subject + Observer) ---")
val stok = Subject<Int>()
stok.attach(StokLogger())
stok.attach(StokAlerm(10))
stok.setState(50)
stok.setState(8)
stok.setState(20)
println()
println("--- 2. Idiomatic Kotlin: function-based observer ---")
val nama = Observable("Budi")
nama.subscribe { println(" Listener 1: nama menjadi $it") }
nama.subscribe { println(" Listener 2: nama berada di state $it") }
nama.value = "Siti"
nama.value = "Dewi"
println()
println("--- 3. StateFlow-like ---")
val stateFlow = StateFlow(0)
stateFlow.collect { println(" Collector A: $it") }
stateFlow.emit(1)
stateFlow.emit(2)
stateFlow.collect { println(" Collector B (mulai belakangan): $it") }
stateFlow.emit(3)
}Output yang Dihasilkan:
=== Demo Observer Pattern ===
--- 1. Classic Observer (Subject + Observer) ---
[logger] Stok sekarang: 50
[logger] Stok sekarang: 8
[alert] Stok di bawah 10: 8
[logger] Stok sekarang: 20
--- 2. Idiomatic Kotlin: function-based observer ---
Listener 1: nama menjadi Budi
Listener 2: nama berada di state Budi
Listener 1: nama menjadi Siti
Listener 2: nama berada di state Siti
Listener 1: nama menjadi Dewi
Listener 2: nama berada di state Dewi
--- 3. StateFlow-like ---
Collector A: 0
Collector A: 1
Collector A: 2
Collector B (mulai belakangan): 2
Collector A: 3
Collector B (mulai belakangan): 3Bedah Kode Baris-per-Baris #
Mari kita amati bagian kunci:
- Baris 5-29 (
Subject+Observer): Pola klasik GoF.Subjectmenyimpan daftar observer dan state. SaatsetStatedipanggil, semua observer diberi tahu via methodupdate. Methodattachdandetachmemungkinkan observer mendaftar dan keluar saat runtime. - Baris 32-49 (
Observable): Versi function-based. Listener adalah lambda(T) -> Unit. Methodsubscribelangsung memanggil listener dengan nilai saat ini, mirip denganBehaviorSubjectdi RxJava. Settervaluehanya memberi tahu listener bila nilai benar-benar berubah. - Baris 52-67 (
StateFlow): Versi sederhana darikotlinx.coroutines.flow.StateFlow. State terakhir disimpan sehingga subscriber baru langsung mendapatkan nilai terbaru. MethodcollectmensimulasikancollectpadaFlowasli, namun tanpa dukungan suspend dan structured concurrency. - Baris 70-82 (concrete observer):
StokLoggerselalu mencatat perubahan;StokAlermhanya bereaksi bila stok di bawah threshold. Pola ini memungkinkan beberapa observer dengan perilaku berbeda merespon sumber data yang sama. - Baris 99-110 (pemakaian
StateFlow): Collector A mulai mendengarkan dari state awal 0. Setelah emit 1 dan 2, Collector B mendaftar dan langsung menerima nilai terakhir (2). Emit berikutnya (3) memberi tahu kedua collector. Perilaku ini persis sepertiStateFlowasli.
Pilihan untuk Produksi #
Untuk aplikasi nyata, pilih berdasarkan kebutuhan concurrency:
| Kebutuhan | Pilihan |
|---|---|
| Listener UI sederhana (klik, hover) | Function-based subscriber |
| State management Android | StateFlow + collectAsState |
| Stream event dengan backpressure | SharedFlow atau Flow |
| Event bus lintas modul | SharedFlow dengan replay 0 |
Penutup #
Observer Pattern adalah fondasi pemrograman reaktif modern. Kotlin menawarkan tiga tingkat abstraksi: interface klasik untuk observer kompleks, function-based untuk listener sederhana, dan StateFlow untuk state management produksi. Pada chapter berikutnya kita akan membahas Adapter Pattern: bagaimana menyatukan interface yang tidak kompatibel lewat class adapter dan extension function.
Referensi #
Referensi Lanjutan #
- Kotlin documentation home
— halaman utama dokumentasi Kotlin untuk
FlowdanSharedFlowsebagai implementasi modern observer pattern