Observer
Also known as¶
- Dependents
Intent¶
Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
Explanation¶
Real-world example¶
In a land far away live the races of hobbits and orcs. Both of them are mostly outdoors, so they closely follow the weather changes. One could say they are constantly observing the weather.
In plain words¶
Register as an observer to receive state changes from the subject you are interested in.
Wikipedia says¶
The observer pattern is a software design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods.
Sequence diagram¶
sequenceDiagram
participant Weather
participant Orcs
participant Hobbits
Weather->>Weather: timePasses()
Weather->>Orcs: update(WeatherType)
Weather->>Hobbits: update(WeatherType)
Programmatic Example¶
Let's first introduce the WeatherObserver functional
interface and our races, Orcs and Hobbits.
fun interface WeatherObserver {
fun update(currentWeather: WeatherType)
}
internal class Orcs : WeatherObserver {
private val logger =
LoggerFactory.getLogger(javaClass)
override fun update(currentWeather: WeatherType) {
logger.info(
"The orcs are facing {} weather now",
currentWeather.description,
)
}
}
internal class Hobbits : WeatherObserver {
private val logger =
LoggerFactory.getLogger(javaClass)
override fun update(currentWeather: WeatherType) {
logger.info(
"The hobbits are facing {} weather now",
currentWeather.description,
)
}
}
Then here is the Weather that is constantly changing.
class Weather {
private val logger =
LoggerFactory.getLogger(javaClass)
private val observers =
mutableListOf<WeatherObserver>()
private var currentWeather = WeatherType.SUNNY
fun addObserver(observer: WeatherObserver) {
observers.add(observer)
}
fun removeObserver(observer: WeatherObserver) {
observers.remove(observer)
}
fun timePasses() {
val values = WeatherType.entries
currentWeather =
values[
(currentWeather.ordinal + 1) % values.size,
]
logger.info(
"The weather changed to {}.",
currentWeather,
)
notifyObservers()
}
private fun notifyObservers() {
observers.forEach { it.update(currentWeather) }
}
}
Here is the full example in action.
val weather = Weather()
weather.addObserver(Orcs())
weather.addObserver(Hobbits())
weather.timePasses()
weather.timePasses()
weather.timePasses()
weather.timePasses()
Program output:
The weather changed to rainy.
The orcs are facing Rainy weather now
The hobbits are facing Rainy weather now
The weather changed to windy.
The orcs are facing Windy weather now
The hobbits are facing Windy weather now
The weather changed to cold.
The orcs are facing Cold weather now
The hobbits are facing Cold weather now
The weather changed to sunny.
The orcs are facing Sunny weather now
The hobbits are facing Sunny weather now
A generic-observer variant uses type parameters so that the observer receives both the subject reference and an argument describing the change:
fun interface Observer<S, A> {
fun update(subject: S, argument: A)
}
abstract class Observable<S : Observable<S, A>, A> {
private val observers =
mutableListOf<Observer<S, A>>()
fun addObserver(observer: Observer<S, A>) {
observers.add(observer)
}
fun removeObserver(observer: Observer<S, A>) {
observers.remove(observer)
}
fun notifyObservers(argument: A) {
observers.forEach {
it.update(this as S, argument)
}
}
}
Class diagram¶
classDiagram
class WeatherType {
<<enumeration>>
COLD
RAINY
SUNNY
WINDY
+description String
+toString() String
}
class WeatherObserver {
<<fun interface>>
+update(WeatherType)
}
class Weather {
-observers MutableList~WeatherObserver~
-currentWeather WeatherType
+addObserver(WeatherObserver)
+removeObserver(WeatherObserver)
+timePasses()
-notifyObservers()
}
class Hobbits {
+update(WeatherType)
}
class Orcs {
+update(WeatherType)
}
Weather --> WeatherObserver : notifies
Hobbits ..|> WeatherObserver
Orcs ..|> WeatherObserver
class Observer~S, A~ {
<<fun interface>>
+update(S, A)
}
class Observable~S, A~ {
<<abstract>>
-observers MutableList~Observer~
+addObserver(Observer)
+removeObserver(Observer)
+notifyObservers(A)
}
class Race {
<<fun interface>>
}
class GenWeather {
-currentWeather WeatherType
+timePasses()
}
class GenHobbits {
+update(GenWeather, WeatherType)
}
class GenOrcs {
+update(GenWeather, WeatherType)
}
GenWeather --|> Observable
Race --|> Observer
GenHobbits ..|> Race
GenOrcs ..|> Race
Applicability¶
Use the Observer pattern when:
- An abstraction has two aspects, one dependent on the other. Encapsulating these aspects in separate objects lets you vary and reuse them independently.
- A change to one object requires changing others, and you don't know how many objects need changing.
- An object should be able to notify other objects without making assumptions about who those objects are -- you don't want these objects tightly coupled.
Consequences¶
Benefits:
- Promotes loose coupling between the subject and its observers.
- Allows dynamic subscription and unsubscription of observers at runtime.
Trade-offs:
- Can lead to memory leaks if observers are not properly deregistered.
- The order of notification is not specified, which may lead to unexpected behaviour.
- Potential for performance issues with a large number of observers.