โ† Kembali ke Design Pattern
๐Ÿ‘๏ธ
Behavioral

Observer

Mendefinisikan ketergantungan one-to-many sehingga saat state satu objek berubah, semua objek yang bergantung padanya otomatis diberi tahu.

Masalahnya

Beberapa bagian aplikasi perlu bereaksi saat data bersama berubah, tetapi kamu tidak ingin sumber datanya mengenal atau terikat erat dengan para konsumennya.

Cara Kerja

  • โ€บSubject (publisher) menyimpan daftar observer (subscriber)
  • โ€บLoose coupling: subject tidak mengenal class observer yang konkret
  • โ€บModel push mengirim data bersama notifikasi; model pull membiarkan observer mengambilnya sendiri
  • โ€บBerisiko memory leak jika observer tidak di-unsubscribe (gunakan weak reference)
  • โ€บDikenal juga sebagai pattern Publish/Subscribe atau Event Listener

Kapan Digunakan

โœ“UI yang harus diperbarui saat data di baliknya berubah (data binding)
โœ“Sistem event di mana beberapa komponen bereaksi terhadap event yang sama
โœ“Sistem notifikasi, analytics tracking, logging ke banyak target
โœ“Fondasi reactive programming (Combine, RxSwift, LiveData)

Contoh di Dunia Nyata

๐ŸŽ
iOS

NotificationCenter, Combine Publisher/Subscriber, KVO, @Published + @ObservedObject

๐Ÿค–
Android

LiveData.observe(), StateFlow.collect(), RxJava Observable, EventBus

๐ŸŒ
Web

EventEmitter (Node.js), RxJS Observable, Vue reactivity, React useEffect

Contoh Kode

Swift
protocol StoreObserver: AnyObject {
    func storeDidUpdate<T>(_ newValue: T)
}

class Store<T> {
    private var observers = [WeakBox]()
    var value: T { didSet { notify(value) } }

    init(_ value: T) { self.value = value }

    func subscribe(_ observer: StoreObserver) {
        observers.append(WeakBox(observer))
    }

    private func notify(_ newValue: T) {
        observers.forEach { $0.value?.storeDidUpdate(newValue) }
    }

    private class WeakBox {
        weak var value: StoreObserver?
        init(_ v: StoreObserver) { value = v }
    }
}

class CounterLabel: StoreObserver {
    func storeDidUpdate<T>(_ newValue: T) {
        print("Label updated: \(newValue)")
    }
}

let store = Store(0)
let label = CounterLabel()
store.subscribe(label)
store.value = 5   // "Label updated: 5"
store.value = 10  // "Label updated: 10"