The Observer Design Pattern is a behavioral pattern that defines a one-to-many dependency between objects so that when one object (the subject) changes its state, all its dependents (observers) are automatically notified and updated.
It’s particularly useful in situations where:
A straightforward approach is to directly call update methods on other objects whenever something changes. For example, a NewsPublisher
might call update()
on a MobileApp
, EmailService
, and WebsiteFeed
every time a new article is published.
But as the number of subscribers grows, this approach becomes rigid, hard to scale, and violates the Open/Closed Principle — adding or removing subscribers requires modifying the publisher class. It also tightly couples the publisher to all its subscribers.
The Observer Pattern solves this by decoupling the subject and its observers, allowing them to interact through a common interface. Observers can be added or removed at runtime, and the subject doesn’t need to know who they are — just that they implement a specific interface.
Let’s walk through a real-world example to see how we can apply the Observer Pattern to build a flexible, extensible, and loosely coupled notification system that’s perfect for event-driven applications.
Imagine you're developing a Fitness Tracker App that connects to a wearable device and receives real-time fitness data.
This data includes:
Whenever new data is received from the device, it gets pushed into a central object — let’s call it FitnessData
.
Now, multiple modules within your app need to react to these updates:
Displays real-time stats (e.g., step count, active minutes) on the app's dashboard.
Periodically logs fitness data to a database or file for historical trend analysis.
Sends alerts to the user, such as:
In a simple approach, the FitnessData
object directly holds and manages references to all its dependent modules.
The FitnessDataNaive
class is tightly coupled to the specific implementations of all observers. It must know about all dependent modules, maintain direct references to them, and call their methods manually.
If you change or replace one of these modules, you’ll likely need to modify
FitnessDataNaive
too.
What happens when you want to add a new feature — say, a WeeklySummaryGenerator
?
You’ll have to:
FitnessDataNaive
newFitnessDataPushed()
method to call itThis means every time a new observer is added, the subject class must be modified — clearly violating the Open/Closed Principle.
Modules like the NotificationService
or ProgressLogger
can’t be added or removed at runtime. What if the user disables notifications in their settings?
You’ll need to add even more conditionals to manually enable/disable parts of the code — making things fragile and error-prone.
The FitnessDataNaive
class, which should be focused solely on managing fitness metrics, is now responsible for:
It’s doing too much, violating the Single Responsibility Principle and making the class harder to test, maintain, or reuse.
As the number of modules grows, the newFitnessDataPushed()
method turns into a giant block of update calls. Even worse, each update might require specific parameters or conditional logic, making it increasingly complex and rigid.
We need a better, scalable way to solve this problem — something that allows:
FitnessData
to broadcast changes to multiple listeners, without knowing who they areEnter the Observer Design Pattern — the perfect solution for this scenario.
The Observer Design Pattern provides a clean and flexible solution to the problem of broadcasting changes from one central object (the Subject) to many dependent objects (the Observers) — all while keeping them loosely coupled.
In our Fitness Tracker App, the Observer Pattern allows the FitnessData
object to notify all registered modules (like LiveActivityDisplay
, ProgressLogger
, and GoalNotifier
) automatically whenever new fitness data is received — without needing to know what those modules are or how they respond.
FitnessDataObserver
)update()
method.update()
to respond to updates.FitnessDataSubject
)Declares methods to:
registerObserver()
– subscribe to updatesremoveObserver()
– unsubscribe from updatesnotifyObservers()
– notify all current observers of a changeThe subject doesn't care who the observers are — it just sends updates.
FitnessData
)FitnessDataSubject
.FitnessDataObserver
objects.notifyObservers()
to broadcast the change.LiveActivityDisplay
)FitnessDataObserver
interface.update()
is called, each observer pulls relevant data from the subject and performs its own logic (e.g., update UI, log progress, send alerts).Let’s refactor our fitness tracker system using the Observer Pattern.
FitnessDataObserver
InterfaceEach observer gets a reference to the FitnessData
subject and can pull any information it needs.
FitnessDataSubject
InterfaceThis interface allows observers to register and unregister themselves, and lets the subject notify all observers of changes.
FitnessData
Class (ConcreteSubject)This class will hold the actual fitness data, manage a list of observers, and notify them upon data changes.
Each observer defines its own update()
logic based on the new fitness data.
Now we wire everything together and simulate some fitness updates:
FitnessData
doesn’t care who is listening — it just broadcastsWeeklySummaryGenerator
) only requires implementing FitnessDataObserver
— no changes to FitnessData