1. The Problem
Problem: One Change Must Update Many Dependents
Software often has situations where one object changes and many other objects must react to that change. Examples: UI widgets reacting to data changes, event streams, message broadcasting, sensor updates.
Naive approach: manually call each dependent:
observerA.update(data)
observerB.update(data)
observerC.update(data)
Problems:
- Publisher must know every observer → tight coupling
- Adding/removing observers requires modifying the publisher
- Scaling to many observers becomes unmanageable
- Violates Open/Closed: adding a new listener requires publisher changes
We need automatic, decoupled, scalable broadcasting of state changes without the publisher depending on concrete observers.
2. The Observer Pattern: Decouple Publishers from Subscribers
Observer pattern decouples who generates data from who reacts to it.
Core idea:
- One Observable publishes changes
- Many Observers subscribe
- When state updates, publisher notifies all observers
- Publisher does not know observers’ concrete types
Components:
- Observable — maintains a list of observers and notifies them
- Observer — interface defining
update() - Concrete Observers — react to updates
- Concrete Observable — triggers notifications when state changes
Benefits:
- Full decoupling between publisher and subscribers
- Dynamic subscription/detachment
- Open/Closed compliance (add new observers without touching publisher)
- Push-based event propagation
- Natural fit for GUIs, event systems, reactive code
Use Observer when:
- State changes must propagate to multiple dependents
- Subscribers vary independently from the publisher
- You need plug-in listeners or extensible event handling
- You want to avoid hard-coded notification chains
- You need real-time or reactive updates
3. Implementation: Observable + Observer in Python
Interfaces
from abc import ABC, abstractmethod
class Observer(ABC):
@abstractmethod
def update(self, data: dict[str, str]):
pass
class Observable(ABC):
@abstractmethod
def attach(self, observer: Observer):
pass
@abstractmethod
def detach(self, observer: Observer):
pass
@abstractmethod
def notify(self):
pass
Display abstraction
class Display(ABC):
@abstractmethod
def display(self, data: dict[str, str]):
pass
class LoggingDisplay(Display):
def display(self, data: dict[str, str]):
for key, value in data.items():
print(key.title(), ": ", value)
Concrete Observers
class SubscriberA(Observer, LoggingDisplay):
def __init__(self, subject: Observable) -> None:
self.subject = subject
self.data = {}
subject.attach(self)
def update(self, data: dict[str, str]):
for key, value in data.items():
data[key] = f"A: {value}"
self.data = data
self.display(data=data)
def detach(self):
self.subject.detach(self)
class SubscriberB(Observer, LoggingDisplay):
def __init__(self, subject: Observable) -> None:
self.subject = subject
self.data = {}
subject.attach(self)
def update(self, data: dict[str, str]):
for key, value in data.items():
data[key] = f"B: {value}"
self.data = data
self.display(data=data)
def detach(self):
self.subject.detach(self)
Concrete Publisher
class PublisherA(Observable):
def __init__(self) -> None:
self.subscribers: list[Observer] = []
self.value: int = 0
def attach(self, observer: Observer):
self.subscribers.append(observer)
def detach(self, observer: Observer):
self.subscribers.remove(observer)
def notify(self):
for sub in self.subscribers:
sub.update(data={"value": str(self.value)})
print("-----")
def update(self, value):
self.value = value
self.notify()
Usage
publisher = PublisherA()
subscribera = SubscriberA(publisher)
subscriberb = SubscriberB(publisher)
publisher.update(10)
publisher.update(20)
subscribera.detach() # dynamic removal
publisher.update(30)
4. When to Use Observer (and When Not To)
Use Observer when:
- Many dependents need automatic state updates
- You need plugin-style subscribers
- You want event-driven or reactive behavior
- Subscribers should be detachable at runtime
- Publisher must not depend on subscriber types
Avoid Observer when:
- Updates must be synchronous and tightly controlled
- Subscribers require a guaranteed order of execution
- The system is simple and a direct method call is enough
- Observers require shared state that leads to hidden coupling
- Overuse would create unnecessary indirection