June 17, 2026 in patterns4 minutes
A intermediate-level guide to Observer: before-and-after java code and diagrams for a CS student.
Imagine you are building a weather station. When the temperature changes, you need to update a digital display, log the data to a file, and perhaps trigger an alarm if it gets too hot. At first, this seems easy: you just call display.update() and logger.log() inside your sensor class.
However, as you add more features—like a mobile app notification or a web dashboard—your sensor class begins to bloat. It must now “know” about every single component that needs the temperature data. This is known as tight coupling: the TemperatureSensor cannot exist or be tested without also bringing along its specific dependents. If you change how the logger works, you have to modify the sensor.
class TemperatureSensor {
private double temperature;
public void setTemperature(double temperature) {
this.temperature = temperature;
// Tight coupling: Sensor must know about every specific dependent
updateLabel();
logToConsole();
}
private void updateLabel() {
System.out.println("UI Label updated to: " + temperature);
}
private void logToConsole() {
System.out.println("Log entry: Temp is " + temperature);
}
}In this design, the sensor has a direct dependency on concrete implementations like a UI label and a console logger. It is doing too much work; it is not just sensing temperature, it is managing its own list of dependents.
classDiagram
class TemperatureSensor {
-double temperature
}
class UpdateLabelMethod {
}
class LogToConsoleMethod {
}
TemperatureSensor ..> UpdateLabelMethod : calls
TemperatureSensor ..> LogToConsoleMethod : calls
Think of a YouTube channel. When a creator uploads a new video, they do not manually call every single subscriber on the phone to tell them about it. Instead, the creator simply “broadcasts” the update to a list of subscribers.
The creator does not know if you are watching on a phone or a laptop, nor do they care what you do with the video after you watch it. They only care that you have joined their subscription list. This is the essence of the Observer pattern: a “Subject” maintains a list of “Observers” and notifies them when its state changes.
To fix the coupling in our sensor, we introduce an interface. An interface acts as a contract; it tells the sensor what can be done without telling it how to do it. Instead of the sensor knowing about specific classes like Label or Logger, it only knows about the TemperatureObserver interface.
import java.util.ArrayList;
import java.util.List;
interface TemperatureObserver {
void onTemperatureChanged(double newTemperature);
}
class TemperatureSensor {
private final List<TemperatureObserver> observers = new ArrayList<>();
private double temperature;
public void addObserver(TemperatureObserver observer) {
observers.add(observer);
}
public void removeObserver(TemperatureObserver observer) {
observers.remove(observer);
}
public void setTemperature(double temperature) {
this.temperature = temperature;
notifyObservers();
}
private void notifyObservers() {
for (TemperatureObserver observer : observers) {
observer.onTemperatureChanged(temperature);
}
}
}By using an interface, we have achieved several things:
TemperatureSensor no longer knows which specific classes are listening to it. It only knows they implement onTemperatureChanged.SmsAlertObserver without ever touching the code inside TemperatureSensor. This follows the Open-Closed Principle: classes should be open for extension but closed for modification.To see how this works during execution, observe the sequence of events when a temperature change occurs:
sequenceDiagram
participant S as TemperatureSensor
participant O1 as LabelObserver
participant O2 as LoggerObserver
S ->> S: setTemperature(25.0)
S ->> O1: onTemperatureChanged(25.0)
O1 ->> O1: updateDisplay()
S ->> O2: onTemperatureChanged(25.0)
O2 ->> O2: writeToFile()
The Observer pattern is ideal for implementing distributed event-handling systems or building reactive UIs where a change in data must reflect across multiple disparate components simultaneously.
However, use it with caution:
Using the Observer pattern to attach multiple decoupled dependents
TemperatureSensor sensor = new TemperatureSensor();
sensor.addObserver(temp -> System.out.println("UI: " + temp));
sensor.addObserver(temp -> System.out.println("Log: " + temp));
sensor.setTemperature(25.5);