83. Observer Pattern
The Observer pattern defines a one-to-many dependency so that when one object changes state, all its dependents are notified; java.util.Observable is deprecated โ use event listeners, PropertyChangeSupport, or a reactive library.
Explanation
The Observer (or Publish-Subscribe) pattern has a Subject that maintains a list of Observers and notifies them when its state changes. Every GUI framework, event system, and reactive library is built on this idea.
java.util.Observable and java.util.Observer were deprecated in Java 9 because Observable is a class (forces inheritance), not thread-safe by default, and passes events as generic Object rather than typed events. Do not use them.
Modern Java alternatives: (1) Manual observer: simple functional interface list, often sufficient. (2) PropertyChangeSupport (from java.beans): for JavaBean-style property change events. (3) Flow API (Java 9+): reactive streams with backpressure. (4) Event buses (Guava EventBus, Spring ApplicationEventPublisher): for decoupled, cross-module events.
Example
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
public class ObserverPattern {
// Modern manual observer: typed, lambda-friendly
static class EventBus<T> {
private final List<Consumer<T>> listeners = new ArrayList<>();
public void subscribe(Consumer<T> listener) { listeners.add(listener); }
public void unsubscribe(Consumer<T> listener) { listeners.remove(listener); }
public void publish(T event) { listeners.forEach(l -> l.accept(event)); }
}
record StockPrice(String ticker, double price) {}
// PropertyChangeSupport: JavaBean style
static class Temperature {
private double celsius;
private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
public void addPropertyChangeListener(PropertyChangeListener l) { pcs.addPropertyChangeListener(l); }
public void setCelsius(double celsius) {
double old = this.celsius;
this.celsius = celsius;
pcs.firePropertyChange("celsius", old, celsius);
}
public double getCelsius() { return celsius; }
}
public static void main(String[] args) {
// Manual typed event bus
EventBus<StockPrice> stockBus = new EventBus<>();
Consumer<StockPrice> logger = p -> System.out.printf("LOG: %s = $%.2f%n", p.ticker(), p.price());
Consumer<StockPrice> alerter = p -> { if (p.price() > 200) System.out.println("ALERT: " + p.ticker() + " crossed $200!"); };
stockBus.subscribe(logger);
stockBus.subscribe(alerter);
stockBus.publish(new StockPrice("JAVA", 150.00));
stockBus.publish(new StockPrice("JAVA", 215.50)); // triggers alert
stockBus.unsubscribe(alerter);
stockBus.publish(new StockPrice("JAVA", 100.00)); // no alert (unsubscribed)
System.out.println();
// PropertyChangeSupport
Temperature temp = new Temperature();
temp.addPropertyChangeListener(evt ->
System.out.printf("Temp changed: %.1f -> %.1fยฐC%n",
(double) evt.getOldValue(), (double) evt.getNewValue()));
temp.setCelsius(20.0);
temp.setCelsius(25.5);
}
}Sources
- https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/beans/PropertyChangeSupport.html โ PropertyChangeSupport API
- https://refactoring.guru/design-patterns/observer โ Observer pattern reference