Second pattern this week: Observer. The setup was a lane control system โ sensor detects a lane status change, and multiple components (speed controller, warning system, logger) all need to react. The naive version calls each component directly from the subject, which hard-codes every dependency. Observer breaks that coupling.
- The key insight is that the subject shouldn't care who reacts or how โ it just maintains a list and notifies everyone; each observer decides what to do with the event
interface LaneObserver {
fun onLaneStatusChanged(status: LaneStatus)
}
class LaneController {
private val observers = mutableListOf<LaneObserver>()
private var currentStatus: LaneStatus = LaneStatus.CLEAR
fun subscribe(observer: LaneObserver) = observers.add(observer)
fun unsubscribe(observer: LaneObserver) = observers.remove(observer)
fun updateLane(newStatus: LaneStatus) {
if (newStatus == currentStatus) return // skip duplicate notifications
currentStatus = newStatus
observers.forEach { it.onLaneStatusChanged(currentStatus) }
}
}- Adding a new component that reacts to lane changes is one step: implement
LaneObserverand subscribe โLaneControllernever changes, which is Open/Closed in action - Deduplication matters: checking
if (newStatus == currentStatus) returnbefore notifying prevents observers from firing on a no-op update - Runtime subscribe/unsubscribe means observers can come and go โ useful when a component is conditionally active (e.g., warning system is disabled in certain modes)
class SpeedController : LaneObserver {
override fun onLaneStatusChanged(status: LaneStatus) {
val speed = when (status) {
LaneStatus.CLEAR -> "Full speed"
LaneStatus.MERGING -> "Reduce to 30mph"
LaneStatus.BLOCKED, LaneStatus.CONSTRUCTION -> "Stop"
}
println("[SpeedController] $speed")
}
}- When sensor data arrives asynchronously, the sync Observer model isn't enough โ the upgrade path is replacing
updateLane()with aMutableStateFlowso the ViewModel can collect lane updates onDispatchers.IOand expose them lifecycle-safely withcollectAsStateWithLifecycle - Observer pattern and
StateFlowsolve the same core problem at different layers: Observer for class-to-class notification in pure Kotlin, StateFlow for ViewModel-to-UI notification in Android - Testing is straightforward: mock the observer, call
updateLane(), andverifythatonLaneStatusChanged()was called with the right status โ MockK handles this in two lines