Simple explanation for Observer Pattern

Situation: You Built a Stock Price Updater

You have a StockPriceService that updates the latest price for a stock.

Initially, you just print the new price to the console. All good.

stockPriceService.update("AAPL", 174.22);

Then Product Demands Pile Up

  • Send price updates to mobile app
  • Send price updates to dashboard UI
  • Send price updates to Slack channel
  • Store it in database
  • Push to analytics service

Now your method starts looking like this:

void updatePrice(String stock, double price) {
    logToConsole();
    updateUI();
    notifyMobileApp();
    saveToDB();
    sendToSlack();
    triggerAnalytics();
}

What was once clean… is now cluttered chaos. Every new “listener” = more hardcoded logic.

What’s the Problem?

  • Your StockPriceService knows way too much about who’s listening.
  • It’s tightly coupled to every downstream consumer.
  • Adding/removing listeners means editing this one method → violates Open/Closed Principle
  • Testing becomes painful
  • No flexibility - you can’t add or remove subscribers at runtime

How Observer Pattern Saves You

Observer says: “Let the stock price service focus on updates. Anyone who cares about the change can subscribe and be notified automatically.”

So you:

  1. Define an Observer interface (e.g., onPriceUpdate)
  2. Make all interested parties (UI, Slack, DB, etc.) implement this interface
  3. In StockPriceService, you just maintain a list of observers
  4. When price updates → loop and notify all

No hardcoded dependencies. Adding/removing listeners becomes plug-and-play.

Implementation

// 1. Observer interface
interface PriceObserver {
    void onPriceUpdate(String stock, double price);
}

// 2. Observable subject
class StockPriceService {
    private final List<PriceObserver> observers = new ArrayList<>();
    
    public void subscribe(PriceObserver observer) {
        observers.add(observer);
    }
    
    public void unsubscribe(PriceObserver observer) {
        observers.remove(observer);
    }
    
    public void updatePrice(String stock, double price) {
        System.out.println("Updating price for " + stock + ": $" + price);
        for (PriceObserver obs : observers) {
            obs.onPriceUpdate(stock, price);
        }
    }
}

// 3. Concrete observer
class MobileApp implements PriceObserver {
    public void onPriceUpdate(String stock, double price) {
        System.out.println("Mobile received: " + stock + " @ $" + price);
    }
}

// Usage
StockPriceService service = new StockPriceService();
service.subscribe(new MobileApp());

When to Use Observer?

  • When multiple parts of your app react to the same event
  • When you want to decouple the source from its listeners
  • When you need a pub-sub model (event buses, UI updates, streams)

Benefits

  • Decoupling: Subject doesn’t need to know details about its observers
  • Open/Closed: Add new subscribers without modifying the subject
  • Dynamic relationships: Add/remove observers at runtime
  • One-to-many: A single subject can notify multiple observers
  • Loose coupling: Publishers and subscribers can vary independently

Real-World Examples

  • Event handling in UI frameworks
  • Message queues and event buses
  • Notification systems
  • Real-time data monitoring
  • Reactive programming frameworks

Drawbacks

  • If not managed correctly, can lead to unexpected updates
  • Potential memory leaks if observers aren’t unsubscribed
  • Order of notification not guaranteed
  • Debugging can be challenging as execution flow is less obvious

The Observer pattern is your best friend whenever you have a one-to-many relationship where one object’s state change needs to trigger reactions in multiple other objects.