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 #

VarianKarakteristikCocok untuk
Subject + Observer interfaceKlasik GoF, observer sebagai objectObserver kompleks dengan banyak method
Function-based subscriberSubscriber sebagai lambdaListener sederhana (klik tombol, log)
StateFlow-likeState terakhir disimpan, subscriber baru langsung dapat nilai saat iniUI 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 #

kotlin
// 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:

text
=== 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): 3

Bedah Kode Baris-per-Baris #

Mari kita amati bagian kunci:

  1. Baris 5-29 (Subject + Observer): Pola klasik GoF. Subject menyimpan daftar observer dan state. Saat setState dipanggil, semua observer diberi tahu via method update. Method attach dan detach memungkinkan observer mendaftar dan keluar saat runtime.
  2. Baris 32-49 (Observable): Versi function-based. Listener adalah lambda (T) -> Unit. Method subscribe langsung memanggil listener dengan nilai saat ini, mirip dengan BehaviorSubject di RxJava. Setter value hanya memberi tahu listener bila nilai benar-benar berubah.
  3. Baris 52-67 (StateFlow): Versi sederhana dari kotlinx.coroutines.flow.StateFlow. State terakhir disimpan sehingga subscriber baru langsung mendapatkan nilai terbaru. Method collect mensimulasikan collect pada Flow asli, namun tanpa dukungan suspend dan structured concurrency.
  4. Baris 70-82 (concrete observer): StokLogger selalu mencatat perubahan; StokAlerm hanya bereaksi bila stok di bawah threshold. Pola ini memungkinkan beberapa observer dengan perilaku berbeda merespon sumber data yang sama.
  5. 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 seperti StateFlow asli.

Pilihan untuk Produksi #

Untuk aplikasi nyata, pilih berdasarkan kebutuhan concurrency:

KebutuhanPilihan
Listener UI sederhana (klik, hover)Function-based subscriber
State management AndroidStateFlow + collectAsState
Stream event dengan backpressureSharedFlow atau Flow
Event bus lintas modulSharedFlow 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 Flow dan SharedFlow sebagai implementasi modern observer pattern