Skip to content
3 min read

👁️ Observer Design Pattern

Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.


🌍 Real-World Analogy

Analogy — YouTube Subscriptions

Think of a YouTube channel. When you subscribe to a channel, you get notified every time a new video is uploaded. You don't need to keep checking — the channel (Subject) pushes notifications to all subscribers (Observers). You can unsubscribe anytime, and new subscribers can join without affecting others.

%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, -apple-system, sans-serif'}, 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 12, 'curve': 'basis'}, 'sequence': {'actorMargin': 60, 'messageMargin': 40}, 'class': {'padding': 12}}}%%
flowchart LR
    YT["🎬 YouTube Channel"] -->|"uploads video"| N["🔔 Notification"] -->|"reaches"| SA["👤 Subscriber A"]
    N -->|"reaches"| SB["👤 Subscriber B"]

    style YT fill:#FCE4EC,stroke:#C62828,stroke-width:2px,color:#000
    style N fill:#FFF8E1,stroke:#F9A825,color:#000
    style SA fill:#E3F2FD,stroke:#1565C0,color:#000
    style SB fill:#E3F2FD,stroke:#1565C0,color:#000

🏗️ Pattern Structure

%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, -apple-system, sans-serif'}, 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 12, 'curve': 'basis'}, 'sequence': {'actorMargin': 60, 'messageMargin': 40}, 'class': {'padding': 12}}}%%
flowchart LR
    Subject[["📢 Subject"]]
    ConcreteSubject{{"🏢 Concrete Subject"}}
    Observer[["👁️ Observer"]]
    ObsA{{"📱 Observer A"}}
    ObsB{{"💻 Observer B"}}

    ConcreteSubject -->|"implements"| Subject
    Subject -->|"holds ref to"| Observer
    ObsA -->|"implements"| Observer
    ObsB -->|"implements"| Observer
    ConcreteSubject -.->|"notifies"| ObsA
    ConcreteSubject -.->|"notifies"| ObsB

    style Subject fill:#FFF3E0,stroke:#E65100,color:#000
    style ConcreteSubject fill:#E3F2FD,stroke:#1565C0,color:#000
    style Observer fill:#FFF3E0,stroke:#E65100,color:#000
    style ObsA fill:#E8F5E9,stroke:#2E7D32,color:#000
    style ObsB fill:#E8F5E9,stroke:#2E7D32,color:#000

UML Class Diagram

%%{init: {'theme': 'base', 'themeVariables': {'fontSize': '13px', 'fontFamily': 'Inter, -apple-system, sans-serif'}, 'flowchart': {'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 12, 'curve': 'basis'}, 'sequence': {'actorMargin': 60, 'messageMargin': 40}, 'class': {'padding': 12}}}%%
classDiagram
    class EventListener {
        <<interface>>
        +update(String eventType, String data) void
    }
    class EventManager {
        -listeners: Map~String, List~EventListener~~
        +subscribe(String eventType, EventListener listener) void
        +unsubscribe(String eventType, EventListener listener) void
        +notify(String eventType, String data) void
    }
    class StockMarket {
        -events: EventManager
        -prices: Map~String, Double~
        +updatePrice(String symbol, double newPrice) void
        +getEvents() EventManager
    }
    class PriceAlertListener {
        -threshold: double
        +update(String eventType, String data) void
    }
    class DashboardListener {
        +update(String eventType, String data) void
    }

    StockMarket *-- EventManager : contains
    EventManager --> EventListener : notifies
    PriceAlertListener ..|> EventListener
    DashboardListener ..|> EventListener

❓ The Problem

You have an object whose state changes matter to other objects, but:

  • You don't know how many objects care about the changes at compile time
  • You don't want to tightly couple the subject to its dependents
  • Polling for changes wastes resources and creates lag
  • Adding new listeners should not require modifying the subject

Example: A stock trading platform where price changes need to update charts, alerts, portfolio views, and third-party integrations — all independently.

Without This Pattern

Java
public class StockMarket {
    private DashboardDisplay dashboard;
    private PriceAlertSystem alerts;
    private PortfolioView portfolio;
    // Must add a field for EVERY new listener!

    public void updatePrice(String symbol, double newPrice) {
        prices.put(symbol, newPrice);
        // Directly calling every dependent — tightly coupled
        dashboard.refresh(symbol, newPrice);
        alerts.checkThreshold(symbol, newPrice);
        portfolio.recalculate(symbol, newPrice);
        // Adding a new listener? Modify THIS class every time.
    }
}
  • Violates Open/Closed Principle — adding a new subscriber (e.g., analytics service) forces modification of StockMarket
  • Tight couplingStockMarket must know the concrete type and method signature of every dependent
  • Cannot add/remove listeners at runtime — subscribers are hardcoded at compile time
  • Breaks Single ResponsibilityStockMarket now manages both stock data AND notification routing
  • Pain point: Every new feature that needs price updates requires a code change in the subject class, creating merge conflicts and regression risk

✅ The Solution

The Observer pattern establishes a subscription mechanism where:

  1. The Subject maintains a list of observers and provides methods to subscribe/unsubscribe
  2. When state changes, the subject iterates through observers and calls their update() method
  3. Observers implement a common interface so the subject doesn't know their concrete types
  4. This achieves loose coupling — subject and observers can vary independently

💻 Implementation

Java
// Observer interface
public interface EventListener {
    void update(String eventType, String data);
}

// Concrete Subject - Event Manager
public class EventManager {
    private final Map<String, List<EventListener>> listeners = new HashMap<>();

    public EventManager(String... operations) {
        for (String operation : operations) {
            listeners.put(operation, new ArrayList<>());
        }
    }

    public void subscribe(String eventType, EventListener listener) {
        listeners.get(eventType).add(listener);
    }

    public void unsubscribe(String eventType, EventListener listener) {
        listeners.get(eventType).remove(listener);
    }

    public void notify(String eventType, String data) {
        for (EventListener listener : listeners.get(eventType)) {
            listener.update(eventType, data);
        }
    }
}

// Concrete Subject using EventManager
public class StockMarket {
    private final EventManager events;
    private Map<String, Double> prices = new HashMap<>();

    public StockMarket() {
        this.events = new EventManager("priceChange", "newStock");
    }

    public void updatePrice(String symbol, double newPrice) {
        prices.put(symbol, newPrice);
        events.notify("priceChange", symbol + ":" + newPrice);
    }

    public EventManager getEvents() {
        return events;
    }
}

// Concrete Observers
public class PriceAlertListener implements EventListener {
    private final double threshold;

    public PriceAlertListener(double threshold) {
        this.threshold = threshold;
    }

    @Override
    public void update(String eventType, String data) {
        String[] parts = data.split(":");
        double price = Double.parseDouble(parts[1]);
        if (price > threshold) {
            System.out.println("⚠️ ALERT: " + parts[0] + " exceeded $" + threshold);
        }
    }
}

public class DashboardListener implements EventListener {
    @Override
    public void update(String eventType, String data) {
        System.out.println("📊 Dashboard updated: " + data);
    }
}

// Usage
public class Main {
    public static void main(String[] args) {
        StockMarket market = new StockMarket();

        market.getEvents().subscribe("priceChange", new PriceAlertListener(150.0));
        market.getEvents().subscribe("priceChange", new DashboardListener());

        market.updatePrice("AAPL", 155.0);
        // Output:
        // ⚠️ ALERT: AAPL exceeded $150.0
        // 📊 Dashboard updated: AAPL:155.0
    }
}
Java
// Observer pulls data from subject when notified
public interface Observer {
    void update(Observable subject);
}

public abstract class Observable {
    private final List<Observer> observers = new ArrayList<>();

    public void addObserver(Observer o) { observers.add(o); }
    public void removeObserver(Observer o) { observers.remove(o); }

    protected void notifyObservers() {
        for (Observer o : observers) {
            o.update(this);  // Pass self — observer pulls what it needs
        }
    }
}

public class WeatherStation extends Observable {
    private double temperature;
    private double humidity;

    public void setMeasurements(double temp, double humidity) {
        this.temperature = temp;
        this.humidity = humidity;
        notifyObservers();
    }

    public double getTemperature() { return temperature; }
    public double getHumidity() { return humidity; }
}

public class PhoneDisplay implements Observer {
    @Override
    public void update(Observable subject) {
        if (subject instanceof WeatherStation ws) {
            System.out.println("📱 Phone: " + ws.getTemperature() + "°C");
        }
    }
}

🎯 When to Use

  • When changes to one object require changing others, and you don't know how many objects need to change
  • When an object should notify other objects without making assumptions about who they are
  • When you need a publish-subscribe mechanism within your application
  • When you want to decouple the sender of a notification from its receivers
  • When the set of observers changes dynamically at runtime

🏭 Real-World Examples

Framework/Library Usage
Java java.util.Observer Built-in (deprecated in Java 9, but foundational)
Spring ApplicationEventPublisher Application events and @EventListener
Java Swing ActionListener, MouseListener, all GUI listeners
RxJava / Project Reactor Reactive streams are built on Observer
Kafka Consumer Groups Pub/sub at distributed scale
JavaBeans PropertyChangeListener Property change notifications
Jakarta CDI Events @Observes annotation

⚠️ Pitfalls

Common Mistakes

  • Memory leaks — Forgetting to unsubscribe observers (especially in long-lived subjects). Use WeakReference or explicit cleanup.
  • Unexpected update order — Don't rely on notification order; observers should be independent.
  • Cascade updates — Observer A updates Subject B which notifies Observer C... infinite loops are possible.
  • Thread safetyCopyOnWriteArrayList or synchronization needed in concurrent environments.
  • Performance — Notifying thousands of observers synchronously blocks the thread. Consider async notification.
  • Lapsed listener problem — In GC languages, registered listeners prevent garbage collection.

📝 Key Takeaways

Summary

  • Observer establishes a one-to-many dependency with loose coupling
  • Subject knows observers only through a common interface — Open/Closed Principle
  • Prefer composition (EventManager) over inheritance for subjects
  • In modern Java, consider Flow.Publisher/Flow.Subscriber (Java 9+) or reactive libraries
  • The pattern is the foundation of event-driven architectures and reactive programming
  • Push model sends data; Pull model lets observers fetch what they need — choose based on your use case