Observer: Publishing State Changes Without Coupling
What This Concept Is
Observer lets an object (the subject or publisher) notify other objects (observers or subscribers) of state changes, without knowing who they are or what they will do.
Three moving parts:
- a subject with
subscribe,unsubscribe, andnotify - an observer interface with a single
updatemethod - a wiring step at startup (or dynamically) that connects observers to subjects
The coupling direction matters: the subject depends only on the observer interface. Concrete observer classes can be added later without touching the subject.
Why It Matters Here
Many systems have one source of truth and many things that must react: a domain model and UI widgets, a config and caches that depend on it, a job and its progress listeners.
Without Observer, the source starts calling each listener by name. That direct call list is the coupling that bites you six months later when a new listener type shows up and the publisher has to change.
Concrete Example
A weather station publishing temperature updates to multiple displays.
interface Observer { void update(double tempC); }
class WeatherStation {
private final List<Observer> observers = new ArrayList<>();
private double tempC;
public void subscribe(Observer o) { observers.add(o); }
public void unsubscribe(Observer o) { observers.remove(o); }
public void setTemperature(double t) {
if (t == tempC) return;
tempC = t;
notifyAll(t);
}
private void notifyAll(double t) {
for (Observer o : new ArrayList<>(observers)) {
o.update(t);
}
}
}
class ConsoleDisplay implements Observer {
public void update(double tempC) {
System.out.printf("now %.1f C%n", tempC);
}
}
class AlertService implements Observer {
public void update(double tempC) {
if (tempC > 35.0) System.out.println("HEAT ALERT");
}
}
WeatherStation ws = new WeatherStation();
ws.subscribe(new ConsoleDisplay());
ws.subscribe(new AlertService());
ws.setTemperature(36.2); // both react; WeatherStation knows neither class
WeatherStation never imports ConsoleDisplay or AlertService. A new GraphDisplay observer tomorrow is zero change here.
Common Confusion / Misconception
- "Observer is just callbacks." Technically yes, but the pattern's value is in the explicit subscribe/unsubscribe and the stable
updateinterface that decouples types. - Notifying synchronously feels implicit. The call stack runs through every observer. If one blocks, the subject blocks. This is a feature (ordering, exceptions propagate) but bites under load -- see the next concept for pitfalls.
- Push-only mindset. The
updatemethod can pass the new value directly (push) or pass the subject so the observer pulls what it needs. Both are valid and trade off bandwidth for flexibility. - Observer is not the same as Pub/Sub. Observer is object-to-object inside a process. Pub/Sub usually involves a broker, topics, and often a network boundary. Related, not identical.
How To Use It
- Identify the "one writer, many readers" shape.
- Define a narrow observer interface (
update, or domain-named method). - On the subject, expose
subscribe,unsubscribe, and a guardednotifythat walks a copy of the list. - Decide push vs pull: push simple values, pull when observers need different slices.
- Document re-entrancy guarantees (can an observer subscribe or unsubscribe during
update?). - Wire subjects to observers at composition time; never hardcode them in the subject.
Check Yourself
- Which direction does the dependency between subject and observer point?
- Why walk a copy of the subscriber list during notification?
- What is the difference between push and pull notification?
- How do you add a new kind of observer without modifying the subject?
Mini Drill or Application
Take a logger that currently writes to stdout and also writes to a file via duplicated calls scattered in the caller.
- Extract a
LogEventvalue and aLogSink(observer) interface. - Make the logger the subject with
subscribe(LogSink). - Add a
StdoutSink, aFileSink, and aMemorySinkfor tests. - Show that adding a
NetworkSinklater requires zero changes in the logger.
Stop when the logger imports only the sink interface.