Observer
Let any number of objects subscribe to an event and be told when it happens, without the publisher knowing who they are.
Big idea
Observer lets a subject keep a list of subscribers and notify all of them when something happens. The subject knows only that subscribers have a notify method — not what they do — so you can add reactions without editing the code that triggers them.
The problem it solves
When an order ships, the system must: email the customer, send an SMS, update the analytics dashboard, and tell the warehouse to restock. Written directly in Order.ship():
self.mailer.send(...)
self.sms.send(...)
self.analytics.track(...)
self.warehouse.restock(...)
Order now depends on four unrelated services, can't be tested without them, and every new reaction (loyalty points next quarter) is another edit to core order logic.
Think of it like
A newsletter. You subscribe; the writer publishes; everyone on the list gets the issue. The writer doesn't phone each reader or know what they'll do with it — one forwards it, one files it, one reads it on the train. Readers can unsubscribe anytime and the writer never changes how they publish.
Structure
In code
from collections import defaultdict
class EventBus: # the subject, generalised to topics
def __init__(self):
self._subs = defaultdict(list)
def subscribe(self, topic, handler):
self._subs[topic].append(handler)
return lambda: self._subs[topic].remove(handler) # call to unsubscribe
def publish(self, topic, payload):
for handler in list(self._subs[topic]):
handler(payload)
class Order:
def __init__(self, bus, oid, customer):
self.bus, self.oid, self.customer, self.status = bus, oid, customer, "paid"
def ship(self):
self.status = "shipped"
self.bus.publish("order.shipped", self) # knows no subscribers
bus = EventBus()
bus.subscribe("order.shipped", lambda o: print(f" email {o.customer}: order {o.oid} is on its way"))
stop_sms = bus.subscribe("order.shipped", lambda o: print(f" sms {o.customer}: shipped"))
shipped = []
bus.subscribe("order.shipped", lambda o: shipped.append(o.oid))
print("ship 1:")
Order(bus, 1, "ana").ship()
stop_sms() # ana turned off texts
print("ship 2:")
Order(bus, 2, "ana").ship()
print("analytics saw:", shipped)output
ship 1:
email ana: order 1 is on its way
sms ana: shipped
ship 2:
email ana: order 2 is on its way
analytics saw: [1, 2]Run and edit it in the app.
Adding loyalty points next quarter is one more subscribe call at startup; Order is untouched.
Watch it
Push vs pull, sync vs async
- Push — the event carries the data subscribers need. Pull — the event just says "I changed" and subscribers query the subject.
- Synchronous (above) — subscribers run inside
publish; a slow or failing subscriber slows or breaks the publisher. Isolate failures (try/except per handler) or go asynchronous with a queue — the system-design publish–subscribe model.
In Observer, what does the subject know about its subscribers?
subs = []
def publish(msg):
for s in list(subs):
s(msg)
def once(msg):
print("once got", msg)
subs.remove(once)
subs.append(once)
subs.append(lambda m: print("always got", m))
publish("A")
publish("B")What does it print? Show the answer
once got A
always got A
always got BRun and edit it in the app.
When to use it — and when not to
Use it when one change must trigger an open-ended set of reactions: UI updates from a model, domain events, plug-in hooks, cache invalidation, notifications. Be careful when order matters between subscribers, when you need a reply (that's a request, not an event), or when chains of events cause hard-to-trace cascades.
Pros and cons
| Pros | Cons |
|---|---|
| Publisher decoupled from reactions (Open/Closed) | Notification order is usually unspecified |
| Subscribe and unsubscribe at runtime | Forgotten subscriptions leak memory and do surprising work |
| Natural fit for UIs and domain events | Event cascades are hard to debug |
How it relates
- Mediator routes deliberately between known colleagues; Observer broadcasts to whoever subscribed.
- Chain of Responsibility delivers to one handler in order; Observer delivers to all.
- Command objects can be the events that get published.
- At system scale it's publish–subscribe and event-driven architecture.
A screen subscribes to price updates but never unsubscribes when it closes. What happens?
Key terms
- Observer — A pattern where subscribers are notified automatically when a subject publishes an event.
- Subject (publisher) — The object that keeps a subscriber list and announces changes.
- Subscriber (observer) — An object registered to be notified of events.
- Lapsed listener — A subscriber that was never removed, causing leaks and unwanted work.