Observer
After uploading a video the channel code calls the email sender, the mobile push service and the analytics tracker by name.
The publisher keeps a list of subscribers that all share a tiny interface, update().
One object changes and many others must react
The picture from life
You subscribe to a YouTube channel. When the creator uploads, every subscriber gets a notification. The creator does not know or care who they are, and you can unsubscribe at any time without the creator changing anything.
The problem
What goes wrong when you do not use it.
After uploading a video the channel code calls the email sender, the mobile push service and the analytics tracker by name. Adding a fourth listener means editing the upload code again, and the channel is tied to every listener.
- calls / uses
- One object changes and many others must react
- The set of listeners changes at run time
- The publisher must not depend on its listeners
The idea
How the pattern fixes it.
The publisher keeps a list of subscribers that all share a tiny interface, update(). To announce, it loops through the list. Anyone can subscribe or unsubscribe.
- implements
- calls / uses
Who is who
- Channel (subject)
- Holds subscribers; subscribe(), unsubscribe(), upload().
- Subscriber (observer)
- Interface with update(video).
- Viewer, Analytics...
- Concrete observers reacting in their own way.
Build it in steps
- 1Define the observer interface.
- 2Subject keeps a list and offers subscribe / unsubscribe.
- 3On change, loop and call update().
- 4Observers register themselves.
Watch it run
Step through the calls. Tap an arrow to jump to it.
Asha subscribes to the channel.
subscribe(asha)The code
Python and Java. Try writing the classes yourself from the diagram first.
class Channel: # the subject
def __init__(self):
self.subscribers = []
def subscribe(self, subscriber):
self.subscribers.append(subscriber)
def unsubscribe(self, subscriber):
self.subscribers.remove(subscriber)
def upload(self, video):
for subscriber in list(self.subscribers):
subscriber.update(video) # tell everyone
class Viewer: # an observer
def __init__(self, name):
self.name = name
self.inbox = []
def update(self, video):
self.inbox.append(video)
def demo():
channel, asha, bala = Channel(), Viewer("Asha"), Viewer("Bala")
channel.subscribe(asha)
channel.subscribe(bala)
channel.upload("V1")
channel.unsubscribe(bala)
channel.upload("V2")
return asha.inbox, bala.inboxWhat you gain
- Loose coupling: subject knows only the interface
- Add or remove listeners at run time
What it costs
- Updates arrive in no guaranteed order
- Forgotten unsubscribes leak memory and cause surprise calls
You have already met it in
- Event listeners in the browser (addEventListener)
- Pub/sub systems, Kafka topics, RxJS and Java's PropertyChangeListener
- React state subscriptions
Often mixed up with
- MediatorObserver is a one-way broadcast; Mediator coordinates two-way talk between many objects.
- Chain of ResponsibilityObserver tells all listeners; a chain stops at the first handler that deals with it.
Make it stick
Build a StockTicker that notifies a PriceAlert (threshold) and a Logger. Unsubscribe the alert after it fires once.