Skip to Content
Course content

150: The Observer Pattern

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I want to show you a piece of code that looks perfectly fine at first glance. Imagine you're building a stock market tracker. You have a StockTicker that fetches prices, and you want several different parts of your app—a dashboard, a logger, and an alert system—to react whenever a price changes.

public class StockTicker {
    private double price;
    private Dashboard dashboard;
    private Logger logger;
    private AlertSystem alertSystem;

    public void setPrice(double newPrice) {
        this.price = newPrice;
        // Notify everyone manually
        dashboard.updatePrice(newPrice);
        logger.logPrice(newPrice);
        alertSystem.checkThreshold(newPrice);
    }
    
    // Getters and setters for the components...
}

The Tight Coupling Trap

This code works. But it's a maintenance nightmare waiting to happen. Notice how the StockTicker has to know exactly who is listening to it. It has a hard dependency on Dashboard, Logger, and AlertSystem.

What happens when your boss tells you that we now need a MobilePushNotification service? You have to go back into the StockTicker class, add a new field, update the constructor, and add another line to the setPrice method. You're modifying the core logic of your data source every time you add a new UI element or a side-effect. This is a direct violation of the Open/Closed Principle: your classes should be open for extension, but closed for modification.

Decoupling with the Observer Interface

The fix is to stop the StockTicker from caring about who is listening. Instead, we define a contract—an interface—that any "observer" must follow. We then let the StockTicker maintain a list of these observers without knowing their specific types.

public interface StockObserver {
    void onPriceChange(double price);
}

public class StockTicker {
    private double price;
    private final List<StockObserver> observers = new ArrayList<>();

    public void attach(StockObserver observer) {
        observers.add(observer);
    }

    public void detach(StockObserver observer) {
        observers.remove(observer);
    }

    public void setPrice(double newPrice) {
        this.price = newPrice;
        notifyObservers();
    }

    private void notifyObservers() {
        for (StockObserver observer : observers) {
            observer.onPriceChange(price);
        }
    }
}

Now, the StockTicker is blissfully ignorant of the Dashboard or the Logger. It just knows it has a list of things that implement StockObserver. If you want to add a new notification service, you just create a new class that implements the interface and call ticker.attach(newService). You never have to touch the StockTicker code again.

Handling the "Ghost" Observer Problem

One thing I've seen trip up a lot of developers is forgetting to detach. If you're in a larger application where observers (like UI panels) are created and destroyed frequently, but the Subject (the StockTicker) lives for the entire duration of the app, you've just created a memory leak. The StockTicker still holds a reference to that destroyed panel in its observers list, preventing the Garbage Collector from cleaning it up.

Always make sure that if an object registers itself as an observer, it has a clear lifecycle path to unregister itself. In a real-world Java app, this usually happens in a dispose() or close() method.




📋 Practical Task

Build a Multi-Channel News Agency

You are tasked with building a News Agency system. The agency (the Subject) receives breaking news headlines and must push them to various subscribers (the Observers) without being tightly coupled to the delivery methods.

  • Create a NewsObserver interface with a method update(String news).
  • Create a NewsAgency class that:
    • Maintains a list of NewsObserver objects.
    • Provides methods to subscribe(NewsObserver o) and unsubscribe(NewsObserver o).
    • Has a method publishNews(String news) that notifies all current subscribers.
  • Implement three distinct observer classes:
    • EmailSubscriber: Prints "Sending Email: [news]"
    • SmsSubscriber: Prints "Sending SMS: [news]"
    • NewsAppSubscriber: Prints "Push Notification: [news]"
  • In your main method, simulate the following:
    1. An agency is created.
    2. An email and SMS subscriber join.
    3. A piece of news is published (both should receive it).
    4. The SMS subscriber unsubscribes.
    5. A second piece of news is published (only the email subscriber should receive it).
Rating
0 0

There are no comments for now.

to be the first to leave a comment.