One-line Definition
Establishes a subscription mechanism to notify multiple dependent objects automatically when a publisher's state changes.
Problem
Suppose you are building a weather app. You have a central WeatherData object and multiple displays (Current Conditions, Statistics, Forecast). If the displays constantly poll the WeatherData object to ask "Has the temperature changed?", you waste immense CPU cycles and network bandwidth. If you hardcode the update calls inside WeatherData to explicitly notify each display, you violate the Open/Closed Principle every time you add a new display. The Observer Pattern solves this by introducing a publish-subscribe mechanism, allowing objects to dynamically listen for events.
Real-world Analogy
Think of a YouTube channel. You don't visit the channel every 5 minutes to see if a new video is uploaded. Instead, you click the "Subscribe" button. When the creator (the publisher) uploads a video, YouTube automatically sends a notification to you and every other subscriber (the observers). You can unsubscribe at any time.
Structure
- Client
- Instantiates the Publisher and Observers, then registers the Observers with the Publisher.
- Publisher (Subject)
- Maintains a list of subscribers and sends notifications to them when its state changes.
- Subscriber (Observer)
- Interface declaring an
update()method that gets called by the Publisher.
- Interface declaring an
- ConcreteSubscriber
- Implements the
update()method to react to the state change.
- Implements the
Diagram
classDiagram
class Client {
}
class Publisher {
-List~Subscriber~ subscribers
+subscribe(s: Subscriber)
+unsubscribe(s: Subscriber)
+notifySubscribers()
}
class Subscriber {
<<interface>>
+update(data)
}
class ConcreteSubscriberA {
+update(data)
}
class ConcreteSubscriberB {
+update(data)
}
Client --> Publisher
Client --> ConcreteSubscriberA
Publisher o-- Subscriber : notifies
ConcreteSubscriberA ..|> Subscriber
ConcreteSubscriberB ..|> Subscriber
Code Walkthrough
Notice how the WeatherStation (Publisher) doesn't know anything about PhoneDisplay or WindowDisplay. It just iterates over a list of abstract Subscriber interfaces and calls update().
interface Subscriber {
void update(float temperature, float humidity);
}
class WeatherStation {
private final List<Subscriber> subscribers = new ArrayList<>();
private float temperature;
private float humidity;
public void subscribe(Subscriber s) {
subscribers.add(s);
}
public void unsubscribe(Subscriber s) {
subscribers.remove(s);
}
public void setMeasurements(float temperature, float humidity) {
this.temperature = temperature;
this.humidity = humidity;
notifySubscribers();
}
private void notifySubscribers() {
for (Subscriber s : subscribers) {
s.update(temperature, humidity);
}
}
}
class PhoneDisplay implements Subscriber {
@Override
public void update(float temperature, float humidity) {
System.out.println("📱 Phone Alert: Temp " + temperature + "°C, Humidity " + humidity + "%");
}
}
class WindowDisplay implements Subscriber {
@Override
public void update(float temperature, float humidity) {
System.out.println("🪟 Window Display: It is currently " + temperature + " degrees.");
}
}
class Main {
public static void main(String[] args) {
WeatherStation station = new WeatherStation();
Subscriber phone = new PhoneDisplay();
Subscriber window = new WindowDisplay();
station.subscribe(phone);
station.subscribe(window);
// State changes — automatically pushed to all subscribers
station.setMeasurements(25.5f, 60.0f);
System.out.println("---");
station.unsubscribe(window);
station.setMeasurements(26.0f, 65.0f);
}
}
Bad vs Good
Bad Approach
Problems
- Constant polling wastes resources.
- Hardcoding the updates heavily couples the Publisher to the Concrete Observers.
- Cannot dynamically add or remove displays at runtime.
class WeatherStation {
private PhoneDisplay phone;
private WindowDisplay window;
public void setMeasurements(float temp, float humidity) {
// Tightly coupled! Breaking Open/Closed Principle
phone.update(temp, humidity);
window.update(temp, humidity);
}
}
Better Approach
Improvements
- The Publisher is strictly decoupled from the concrete Observers.
- Subscribers can dynamically register and deregister at runtime.
- Polling is entirely eliminated via push notifications.
// Publisher only knows about the Subscriber interface
public void notifySubscribers() {
for (Subscriber s : subscribers) {
s.update(temperature, humidity);
}
}
Pros vs Cons
| Pros | Cons |
|---|---|
| Enables a robust publish-subscribe architecture | Subscribers are notified in random/unspecified order |
| Adheres strictly to the Open/Closed Principle | Can cause memory leaks if subscribers forget to unsubscribe (Lapsed Listener Problem) |
| Establishes relations dynamically at runtime | Debugging a massive chain of cascading updates is notoriously difficult |
| Completely eliminates the need for polling | |
| Publisher and Subscribers can evolve entirely independently |
When to Use
- Changes to the state of one object must trigger actions in other objects, and the exact list of objects is unknown or changes dynamically.
- You are building UI components that need to react to underlying data model changes (MVC architecture).
- You want to implement an event-driven system without heavy messaging middleware.
When Not to Use
- The objects are tightly bound and always execute sequentially in a fixed, predictable flow.
- A central Mediator would better orchestrate complex workflow logic than scattered events.
Real-world Examples
java.util.Observerandjava.util.Observable(Deprecated in Java 9, but historically the classic example).- Java 9+
java.util.concurrent.FlowAPI (Reactive Streams). - GUI Event Listeners (e.g.,
button.addActionListener()). - Data binding in modern frontend frameworks (React, Vue, Angular).
Key Takeaway
The Observer Pattern creates a dynamic publish-subscribe relationship, allowing an object to notify an open-ended list of dependents when its state changes. Use it to build highly decoupled, event-driven architectures (like UI listeners), but proactively protect against memory leaks by ensuring subscribers always deregister when they are destroyed.