The Observer pattern — decoupling 'what happened' from 'who cares'
When one object's state change needs to notify an unknown, changeable set of other objects, hardcoding the notification list is the thing to avoid — Observer is the pattern that avoids it.
3 min read
The problem: a subject that has to know all its watchers by name
class WeatherStation:
def set_temperature(self, temp):
self.temperature = temp
display.update(temp) # hardcoded dependency
logger.log(temp) # hardcoded dependency
alert_system.check(temp) # hardcoded dependencyEvery time a new kind of thing needs to react to a temperature change, WeatherStation itself has to be edited to call it — the class that owns the data ends up knowing about, and depending directly on, every single consumer of that data. Adding a new consumer, or removing one, means changing code that has nothing conceptually to do with the new consumer's purpose.
The fix: subjects notify a list of observers, generically
class WeatherStation:
def __init__(self):
self._observers = []
self.temperature = None
def subscribe(self, observer):
self._observers.append(observer)
def unsubscribe(self, observer):
self._observers.remove(observer)
def set_temperature(self, temp):
self.temperature = temp
for observer in self._observers:
observer.update(temp) # calls a shared interface, not a specific class
class Display:
def update(self, temp):
print(f"Display: {temp}°")
class Logger:
def update(self, temp):
print(f"Logged: {temp}°")
station = WeatherStation()
station.subscribe(Display())
station.subscribe(Logger())
station.set_temperature(21)Display: 21°
Logged: 21°
WeatherStation (the subject) now knows only that it holds a list of things with an update() method (the observers) — it has no idea how many there are, what they do, or whether they're a display, a logger, or something written next year that doesn't exist yet. Adding AlertSystem is station.subscribe(AlertSystem()) — zero changes to WeatherStation itself.
Why this is genuinely different from just "calling some functions in a loop"
The decoupling is the entire point: the subject depends on an abstraction (anything with update()), not on concrete observer classes, which is the same Dependency Inversion idea covered in the SOLID lesson, applied specifically to the "one change, many reactions" shape. The subject can be tested with a fake observer that just records calls, without needing a real display or a real logger. And the set of observers can change at runtime — subscribing and unsubscribing as the program runs — which a hardcoded sequence of calls can't do at all.
Where this shows up under a different name
This exact shape is what event listeners, pub/sub systems, and Django signals all are: some source of events (element.addEventListener, a Redis pub/sub channel, post_save) that maintains a list of interested parties and notifies each of them generically when something happens, without the event source needing to know who's listening or why. Recognizing "one thing changes, an open-ended set of other things needs to react" as this specific shape is what makes reaching for a subscribe/notify interface automatic, instead of accumulating direct calls in the subject every time a new consumer shows up.
The trade-off it makes
An observer that raises an exception inside update() needs explicit handling (try/except per observer, or the whole notification loop breaks) — the subject can no longer assume every notification succeeds, the way a direct function call's success or failure was previously fully visible at the call site. And the actual order and timing of side effects becomes less obvious from reading set_temperature alone, since what happens next lives in whichever objects happened to subscribe — the same "spread out, less locally readable" cost that Django signals carry, for the same underlying reason.
Further reading
Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. What's the core problem with WeatherStation calling display.update(), logger.log(), and alert_system.check() directly inside set_temperature?
2. After refactoring to Observer, what does WeatherStation actually know about its observers?
3. What's a real cost the Observer pattern introduces, according to this lesson?
4. What everyday mechanisms are actually the Observer pattern under a different name?