Observer Design Pattern
Define a one-to-many dependency so observers are notified of state changes
The Observer Pattern: The Pub-Sub You’ve Been Using All Along
Ever subscribed to a YouTube channel? You hit subscribe, and boom, you get notified when they upload new content. You didn’t have to check their channel every hour. They notify you automatically.
That’s the Observer pattern. One object (the subject) maintains a list of dependents (observers) and notifies them automatically when its state changes.
The Problem: Tight Coupling and Polling
Imagine you’re building a weather station. Multiple displays need to show the weather data: current conditions, statistics, and forecasts.
The naive approach:
class WeatherStation {
private float temperature;
private float humidity;
private float pressure;
public void measurementsChanged() {
float temp = getTemperature();
float humidity = getHumidity();
float pressure = getPressure();
// Directly update all displays
currentConditionsDisplay.update(temp, humidity, pressure);
statisticsDisplay.update(temp, humidity, pressure);
forecastDisplay.update(temp, humidity, pressure);
}
}
Problems:
Tightly coupled: WeatherStation knows about every display. Add a new display? Modify WeatherStation. Remove a display? Modify WeatherStation.
Violates Open/Closed: Can’t add displays without changing existing code.
Not flexible: Can’t add/remove displays at runtime.
Hard to test: Can’t test displays independently.
What if displays could just “subscribe” to the weather station and get notified automatically when data changes?
The Observer Solution
The Observer pattern creates a subscription mechanism:
// Observer interface
interface Observer {
void update(float temperature, float humidity, float pressure);
}
// Subject interface
interface Subject {
void registerObserver(Observer o);
void removeObserver(Observer o);
void notifyObservers();
}
// Concrete Subject
class WeatherStation implements Subject {
private List<Observer> observers;
private float temperature;
private float humidity;
private float pressure;
public WeatherStation() {
observers = new ArrayList<>();
}
public void registerObserver(Observer o) {
observers.add(o);
}
public void removeObserver(Observer o) {
observers.remove(o);
}
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(temperature, humidity, pressure);
}
}
public void measurementsChanged() {
notifyObservers();
}
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
}
// Concrete Observers
class CurrentConditionsDisplay implements Observer {
private float temperature;
private float humidity;
public void update(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
display();
}
public void display() {
System.out.println("Current conditions: " + temperature +
"F degrees and " + humidity + "% humidity");
}
}
class StatisticsDisplay implements Observer {
private List<Float> temperatureHistory = new ArrayList<>();
public void update(float temperature, float humidity, float pressure) {
temperatureHistory.add(temperature);
display();
}
public void display() {
float avg = (float) temperatureHistory.stream()
.mapToDouble(Float::doubleValue)
.average()
.orElse(0.0);
System.out.println("Avg temperature: " + avg + "F");
}
}
Usage:
WeatherStation weatherStation = new WeatherStation();
CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay();
StatisticsDisplay statsDisplay = new StatisticsDisplay();
// Subscribe to weather updates
weatherStation.registerObserver(currentDisplay);
weatherStation.registerObserver(statsDisplay);
// Update weather
weatherStation.setMeasurements(80, 65, 30.4f);
// Both displays automatically update
weatherStation.setMeasurements(82, 70, 29.2f);
// Both displays automatically update again
// Unsubscribe
weatherStation.removeObserver(statsDisplay);
weatherStation.setMeasurements(78, 90, 29.2f);
// Only current display updates
The weather station doesn’t know about specific display types. It just notifies all registered observers. Clean separation. Easy to extend.
The Components
1. Subject (Observable)
The object being observed. Maintains a list of observers and notifies them of changes:
interface Subject {
void registerObserver(Observer o);
void removeObserver(Observer o);
void notifyObservers();
}
2. Observer
Objects that want to be notified of changes:
interface Observer {
void update(/* state data */);
}
3. Concrete Subject
Implements the subject interface and holds the state:
class WeatherStation implements Subject {
private List<Observer> observers;
private float temperature;
// ...
}
4. Concrete Observers
Implement the observer interface and react to updates:
class CurrentConditionsDisplay implements Observer {
public void update(float temp, float humidity, float pressure) {
// Update display
}
}
Real-World Example: Stock Market Ticker
Multiple investors want to track stock prices:
interface StockObserver {
void update(String stock, double price);
}
interface StockMarket {
void registerObserver(StockObserver observer, String stock);
void removeObserver(StockObserver observer, String stock);
void notifyObservers(String stock);
}
class StockExchange implements StockMarket {
// Map of stock symbols to their observers
private Map<String, List<StockObserver>> observers;
private Map<String, Double> stockPrices;
public StockExchange() {
observers = new HashMap<>();
stockPrices = new HashMap<>();
}
public void registerObserver(StockObserver observer, String stock) {
observers.computeIfAbsent(stock, k -> new ArrayList<>()).add(observer);
}
public void removeObserver(StockObserver observer, String stock) {
List<StockObserver> stockObservers = observers.get(stock);
if (stockObservers != null) {
stockObservers.remove(observer);
}
}
public void notifyObservers(String stock) {
List<StockObserver> stockObservers = observers.get(stock);
if (stockObservers != null) {
double price = stockPrices.get(stock);
for (StockObserver observer : stockObservers) {
observer.update(stock, price);
}
}
}
public void setStockPrice(String stock, double price) {
stockPrices.put(stock, price);
notifyObservers(stock);
}
}
class Investor implements StockObserver {
private String name;
public Investor(String name) {
this.name = name;
}
public void update(String stock, double price) {
System.out.println(name + " notified: " + stock + " is now $" + price);
// Investment logic
if (price < 50) {
System.out.println(name + " buying " + stock);
}
}
}
class PortfolioTracker implements StockObserver {
private Map<String, Double> portfolio = new HashMap<>();
public void update(String stock, double price) {
portfolio.put(stock, price);
System.out.println("Portfolio updated: " + stock + " = $" + price);
calculateTotal();
}
private void calculateTotal() {
double total = portfolio.values().stream()
.mapToDouble(Double::doubleValue)
.sum();
System.out.println("Total portfolio value: $" + total);
}
}
Usage:
StockExchange exchange = new StockExchange();
Investor alice = new Investor("Alice");
Investor bob = new Investor("Bob");
PortfolioTracker tracker = new PortfolioTracker();
// Alice watches AAPL and GOOGL
exchange.registerObserver(alice, "AAPL");
exchange.registerObserver(alice, "GOOGL");
// Bob watches AAPL
exchange.registerObserver(bob, "AAPL");
// Tracker watches everything
exchange.registerObserver(tracker, "AAPL");
exchange.registerObserver(tracker, "GOOGL");
// Update stock prices
exchange.setStockPrice("AAPL", 150.0);
// Alice, Bob, and tracker all notified
exchange.setStockPrice("GOOGL", 2800.0);
// Only Alice and tracker notified
Push vs Pull Model
Push Model (What We’ve Been Using)
The subject pushes all data to observers:
interface Observer {
void update(float temperature, float humidity, float pressure);
}
// Subject pushes all data
notifyObservers() {
for (Observer o : observers) {
o.update(temperature, humidity, pressure);
}
}
Pros: Observers get all data without asking Cons: Observers might not need all data, creates coupling
Pull Model
Observers pull data they need:
interface Observer {
void update(Subject subject);
}
class CurrentConditionsDisplay implements Observer {
public void update(Subject subject) {
WeatherStation station = (WeatherStation) subject;
// Pull only what we need
float temp = station.getTemperature();
float humidity = station.getHumidity();
display();
}
}
Pros: Observers get only what they need, less coupling Cons: Observers need to know about subject type
Most real-world implementations use a hybrid approach or pure push for simplicity.
Another Example: Event System
interface EventListener {
void onEvent(Event event);
}
class Event {
private String type;
private Object data;
public Event(String type, Object data) {
this.type = type;
this.data = data;
}
public String getType() { return type; }
public Object getData() { return data; }
}
class EventManager {
private Map<String, List<EventListener>> listeners;
public EventManager() {
listeners = new HashMap<>();
}
public void subscribe(String eventType, EventListener listener) {
listeners.computeIfAbsent(eventType, k -> new ArrayList<>()).add(listener);
}
public void unsubscribe(String eventType, EventListener listener) {
List<EventListener> eventListeners = listeners.get(eventType);
if (eventListeners != null) {
eventListeners.remove(listener);
}
}
public void notify(Event event) {
List<EventListener> eventListeners = listeners.get(event.getType());
if (eventListeners != null) {
for (EventListener listener : eventListeners) {
listener.onEvent(event);
}
}
}
}
// Usage in a game
class Game {
private EventManager eventManager;
public Game() {
eventManager = new EventManager();
setupListeners();
}
private void setupListeners() {
// Achievement system listens to player events
eventManager.subscribe("PLAYER_KILL", event -> {
System.out.println("Achievement unlocked!");
});
// UI listens to score events
eventManager.subscribe("SCORE_CHANGED", event -> {
int score = (int) event.getData();
System.out.println("Score: " + score);
});
// Audio system listens to game events
eventManager.subscribe("PLAYER_KILL", event -> {
System.out.println("Playing kill sound");
});
}
public void playerKilledEnemy() {
// Notify all listeners
eventManager.notify(new Event("PLAYER_KILL", null));
eventManager.notify(new Event("SCORE_CHANGED", 100));
}
}
Java’s Built-In Observer (Legacy)
Java has Observable class and Observer interface (though deprecated as of Java 9):
import java.util.Observable;
import java.util.Observer;
class WeatherData extends Observable {
private float temperature;
public void setTemperature(float temp) {
this.temperature = temp;
setChanged(); // Mark as changed
notifyObservers(); // Notify observers
}
public float getTemperature() {
return temperature;
}
}
class Display implements Observer {
public void update(Observable o, Object arg) {
if (o instanceof WeatherData) {
WeatherData data = (WeatherData) o;
System.out.println("Temperature: " + data.getTemperature());
}
}
}
Why deprecated? Observable is a class, not an interface. Limits flexibility (can’t extend another class). Modern Java uses listeners and event systems instead.
Modern Alternatives
Property Change Listeners (Java Beans)
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
class WeatherStation {
private PropertyChangeSupport support;
private float temperature;
public WeatherStation() {
support = new PropertyChangeSupport(this);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
support.addPropertyChangeListener(listener);
}
public void setTemperature(float temperature) {
float oldTemp = this.temperature;
this.temperature = temperature;
support.firePropertyChange("temperature", oldTemp, temperature);
}
}
// Usage
station.addPropertyChangeListener(evt -> {
System.out.println("Temperature changed from " + evt.getOldValue() +
" to " + evt.getNewValue());
});
Reactive Programming (RxJava)
Modern approach using observables:
// Using RxJava
Observable<Integer> observable = Observable.just(1, 2, 3, 4, 5);
observable.subscribe(
item -> System.out.println("Received: " + item),
error -> System.err.println("Error: " + error),
() -> System.out.println("Complete")
);
When to Use Observer
Use Observer when:
- Changes to one object require changes to others
- You don’t know how many objects need to be updated
- Objects should be loosely coupled
- An abstraction has two aspects, one dependent on the other
Common scenarios:
- Event handling systems
- MVC architecture (Model notifies Views)
- Publish-subscribe messaging
- Stock tickers
- Social media feeds
- Notification systems
- Real-time dashboards
Common Pitfalls
Pitfall 1: Memory Leaks
// BAD: Observer never unregisters
class Display implements Observer {
public Display(WeatherStation station) {
station.registerObserver(this);
// If Display is destroyed but never unregisters,
// station holds a reference, preventing garbage collection
}
}
Solution: Always unregister when done:
class Display implements Observer {
private WeatherStation station;
public Display(WeatherStation station) {
this.station = station;
station.registerObserver(this);
}
public void destroy() {
station.removeObserver(this);
}
}
Pitfall 2: Update Order Dependency
// BAD: Observer B depends on Observer A being updated first
class ObserverA implements Observer {
public void update() {
data.processFirst();
}
}
class ObserverB implements Observer {
public void update() {
// Assumes A already ran!
data.processSecond();
}
}
Observers should be independent. If order matters, you probably need a different pattern.
Pitfall 3: Notification Cascades
// BAD: Observer triggers another notification
class ObserverA implements Observer {
public void update() {
subject.setState(newState); // Triggers another notification!
}
}
Can create infinite loops or performance issues. Be careful with observers modifying the subject.
Observer and SOLID Principles
Single Responsibility Principle
Subject manages observers. Observers handle updates. Separate responsibilities.
Open/Closed Principle
Add new observers without modifying the subject. Open for extension, closed for modification.
Dependency Inversion Principle
Subject depends on Observer interface, not concrete observers. Observers depend on Subject interface, not concrete subjects.
The Mental Model
Think of Observer like:
Newsletter subscription: You subscribe to a newsletter. When new content is published, you get an email. You didn’t check the website every hour. They notified you.
YouTube notifications: Subscribe to a channel. Get notified of new videos. The channel doesn’t know who you are specifically, just that you’re subscribed.
Fire alarm: Fire starts. Alarm goes off. Everyone (observers) hears it and reacts. The alarm doesn’t know who’s in the building.
Performance Considerations
Observer has minimal overhead for small numbers of observers. With hundreds or thousands:
- Consider async notifications
- Batch updates instead of notifying for every change
- Use weak references to prevent memory leaks
- Consider thread safety (covered next)
Thread Safety
If multiple threads modify the subject or observers:
class ThreadSafeSubject {
private final List<Observer> observers =
Collections.synchronizedList(new ArrayList<>());
public void registerObserver(Observer o) {
observers.add(o);
}
public void notifyObservers() {
// Copy list to avoid ConcurrentModificationException
List<Observer> observersCopy;
synchronized(observers) {
observersCopy = new ArrayList<>(observers);
}
for (Observer observer : observersCopy) {
observer.update();
}
}
}
Or use CopyOnWriteArrayList for better read performance.
Final Thoughts
The Observer pattern is everywhere. Every GUI framework, event system, and reactive library uses this concept.
It’s about decoupling. The subject doesn’t know about specific observers. Observers don’t know about each other. Changes propagate automatically.
Key insights:
- One-to-many dependency
- Automatic notification
- Loose coupling
- Dynamic subscriptions
Remember:
- Register and unregister properly
- Keep observers independent
- Watch for memory leaks
- Consider threading
Next time you’re tempted to directly call multiple objects when something changes, stop. Think Observer. Let them subscribe and get notified automatically.
Keep watching.