The Observer pattern creates a one-to-many dependency between objects. When one object (the subject, or publisher) changes state, it notifies and updates every dependent object (the observers, or subscribers). The subject never depends on a concrete observer, so the two sides can change independently.
The problem
You have a subject that changes state, and several unrelated objects need to react to those changes. The naive approach couples the subject to all its dependents. When state changes, the subject calls an update method on each observer. That means every observer must be known at compile time, adding one requires editing the subject, and the subject is hard to reuse in another context.
Observer inverts that dependency. The subject knows only that it has observers. It doesn't know or care what they do. Observers register themselves and handle notifications on their own.
Real-world analogy
Think of a magazine subscription. The magazine is the subject and publishes new issues. Subscribers are the observers and receive each issue automatically. The publisher keeps a mailing list and doesn't care who reads the magazine or what they do with it. The magazine doesn't know individual subscribers, and subscribers don't care how it is printed.
When a new issue comes out, every subscriber gets it. Nobody has to keep asking whether a new issue exists.
Structure
When the subject's state changes, notify() calls update() on every observer.
Key concepts
Push vs. pull notification
Push sends the changed state to observers along with the notification.
subject.notify(new_value); // subject passes data
observer.update(new_value);
Pull only tells observers that something changed. Each observer then queries the subject for state.
subject.notify(); // just says "something changed"
observer.update(); // observer queries subject.get_state()
Push is simpler, but it ties notification to a concrete data type. Pull decouples more, at the cost of making observers know what to query.
Subject-observer relationship
One subject can have many observers. The two sides know as little about each other as possible. Observers attach and detach at runtime, and notification happens without polling.
Implementation approaches
C++ has several ways to implement Observer:
- Traditional Interface Approach uses an abstract observer base class with a virtual
update() - Function-Based (std::function) stores callable objects instead of interface instances
- Qt Signals & Slots is a framework-provided mechanism with metaclass machinery
- Object-Oriented Event System uses parametrized event objects with a type hierarchy
- Lifetime Management handles observer lifetime and prevents dangling pointers
When to use Observer
Use it for: - GUI frameworks where a button click updates several UI components - Model-view separation, where the data model notifies views of changes - Event systems for mouse movement, window resize, or file changes - Publish-subscribe messaging between decoupled modules - Reactive programming, where calculations form a dependency chain
Avoid it when: - You have few tightly bound dependents (direct calls are simpler) - Notification order must be deterministic (observer order is undefined) - Observers need sequenced updates (use explicit orchestration) - Overhead matters (direct calls are faster)
Trade-offs
| Aspect | Benefit | Cost |
|---|---|---|
| Decoupling | Subject and observers vary independently | Indirect communication makes flow harder to follow |
| Dynamic registration | Observers attach and detach at runtime | Nothing guarantees at compile time that all observers get notified |
| Reusability | Subject works with any observer | Observers must know the subject's notification protocol |
| Flexibility | Adding an observer is easy | Order of notification is undefined |
Summary
Observer is worth the indirection when one state change has to reach several objects you do not know at compile time. You pay with indirection and lifetime management. Interface-based, function-based, and framework-provided versions solve the same problem in different ways, so pick the one that matches your language and how loosely coupled the two sides need to be.