AlgoMaster Logo

Observer Design Pattern

Ashish

Ashish Pratap Singh

6 min read

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:

  • You have multiple parts of the system that need to react to a change in one central component.
  • You want to decouple the publisher of data from the subscribers who react to it.
  • You need a dynamic, event-driven communication model without hardcoding who is listening to whom.

A straightforward approach is to directly call update methods on other objects whenever something changes. For example, a NewsPublisher might call update() on a MobileAppEmailService, 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.

1. The Problem: Broadcasting Fitness Data

Imagine you're developing a Fitness Tracker App that connects to a wearable device and receives real-time fitness data.

Smart Watch

This data includes:

  • 👣 Steps taken
  • ⏱️ Active minutes
  • 🔥 Calories burned

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:

LiveActivityDisplay

Displays real-time stats (e.g., step count, active minutes) on the app's dashboard.

ProgressLogger

Periodically logs fitness data to a database or file for historical trend analysis.

NotificationService

Sends alerts to the user, such as:

  • "🎉 Goal achieved!" when they reach 10,000 steps.
  • "⏳ Time to move!" if they’ve been inactive for too long.

In a simple approach, the FitnessData object directly holds and manages references to all its dependent modules.

FitnessDataNaive

FitnessAppNaiveClient

Problems with This Approach

1. Tight Coupling

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.

2. Violates the Open/Closed Principle

What happens when you want to add a new feature — say, a WeeklySummaryGenerator?

You’ll have to:

  • Add a new reference to it inside FitnessDataNaive
  • Update the newFitnessDataPushed() method to call it
  • Possibly change how data flows to it

This means every time a new observer is added, the subject class must be modified — clearly violating the Open/Closed Principle.

3. Inflexible and Static Design

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.

4. Responsibility Bloat

The FitnessDataNaive class, which should be focused solely on managing fitness metrics, is now responsible for:

  • Calling UI update methods
  • Performing data logging
  • Sending notifications

It’s doing too much, violating the Single Responsibility Principle and making the class harder to test, maintain, or reuse.

5. Scalability Bottlenecks

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.

What We Really Need

We need a better, scalable way to solve this problem — something that allows:

  • FitnessData to broadcast changes to multiple listeners, without knowing who they are
  • Each module to subscribe or unsubscribe dynamically
  • Loose coupling between the subject and observers
  • Each module to decide for itself how to respond to changes

Enter the Observer Design Pattern — the perfect solution for this scenario.

2. The Observer Pattern

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 LiveActivityDisplayProgressLogger, and GoalNotifierautomatically whenever new fitness data is received — without needing to know what those modules are or how they respond.

Class Diagram

1. Observer Interface (e.g., FitnessDataObserver)

  • Declares an update() method.
  • All modules that want to listen to fitness data changes will implement this interface.
  • Each observer defines its own logic inside update() to respond to updates.

2. Subject Interface (e.g., FitnessDataSubject)

Declares methods to:

  • registerObserver() – subscribe to updates
  • removeObserver() – unsubscribe from updates
  • notifyObservers() – notify all current observers of a change

The subject doesn't care who the observers are — it just sends updates.

3. ConcreteSubject (e.g., FitnessData)

  • Implements FitnessDataSubject.
  • Maintains an internal list of FitnessDataObserver objects.
  • When new data is pushed, it updates its internal state and calls notifyObservers() to broadcast the change.

4. ConcreteObservers (e.g., LiveActivityDisplay)

  • Implement the FitnessDataObserver interface.
  • When update() is called, each observer pulls relevant data from the subject and performs its own logic (e.g., update UI, log progress, send alerts).

3. Implementing Observer

Let’s refactor our fitness tracker system using the Observer Pattern.

1. Define the FitnessDataObserver Interface

Each observer gets a reference to the FitnessData subject and can pull any information it needs.

2. Define the FitnessDataSubject Interface

This interface allows observers to register and unregister themselves, and lets the subject notify all observers of changes.

3. Implement the FitnessData Class (ConcreteSubject)

This class will hold the actual fitness data, manage a list of observers, and notify them upon data changes.

4. Implement Observer Modules

Each observer defines its own update() logic based on the new fitness data.

LiveActivityDisplay

ProgressLogger

GoalNotifier

5. Client Code

Now we wire everything together and simulate some fitness updates:

What We Achieved

  • Loose Coupling: FitnessData doesn’t care who is listening — it just broadcasts
  • Extensibility: Adding a new module (like WeeklySummaryGenerator) only requires implementing FitnessDataObserver — no changes to FitnessData
  • Runtime Flexibility: Observers can be added/removed dynamically (e.g., based on user settings)
  • Clean Separation of Concerns: Each module is responsible for its own behavior and logic