Java > Design Patterns in Java > Behavioral Patterns > Observer Pattern

Observer Pattern Implementation in Java

The Observer pattern is a behavioral design pattern that defines a one-to-many dependency between objects, so that when one object (the subject) changes state, all its dependents (observers) are notified and updated automatically. This example demonstrates a simple implementation of the Observer pattern in Java.

Conceptual Overview

The Observer pattern involves two key roles: the Subject and the Observer. The Subject maintains a list of Observers, and notifies them of state changes. Observers register with the Subject to receive these notifications. This pattern promotes loose coupling, as the Subject doesn't need to know the specific details of each Observer; it only knows that they implement the Observer interface.

Subject Interface (Observable)

This interface defines the contract for any object that wants to be observable. It includes methods to register, remove, and notify observers.

import java.util.ArrayList;
import java.util.List;

interface Subject {
    void registerObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObservers();
}

Observer Interface

This interface defines the contract for any object that wants to observe a Subject. It includes an update method that is called when the Subject's state changes.

interface Observer {
    void update(String message);
}

Concrete Subject (NewsPublisher)

This class implements the Subject interface. It maintains a list of observers and notifies them when the news is updated. The `setNews` method triggers the notification process.

class NewsPublisher implements Subject {
    private List<Observer> observers = new ArrayList<>();
    private String news;

    @Override
    public void registerObserver(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }

    @Override
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(news);
        }
    }

    public void setNews(String news) {
        this.news = news;
        notifyObservers();
    }
}

Concrete Observer (NewsSubscriber)

This class implements the Observer interface. It receives updates from the Subject and prints them to the console.

class NewsSubscriber implements Observer {
    private String name;

    public NewsSubscriber(String name) {
        this.name = name;
    }

    @Override
    public void update(String news) {
        System.out.println(name + ": Received news: " + news);
    }
}

Client Code (Main)

This class demonstrates how to use the Observer pattern. It creates a Subject (NewsPublisher) and two Observers (NewsSubscribers). It registers the observers with the subject, sets the news, and then removes one of the observers before setting the news again.

public class Main {
    public static void main(String[] args) {
        NewsPublisher publisher = new NewsPublisher();

        NewsSubscriber subscriber1 = new NewsSubscriber("Alice");
        NewsSubscriber subscriber2 = new NewsSubscriber("Bob");

        publisher.registerObserver(subscriber1);
        publisher.registerObserver(subscriber2);

        publisher.setNews("Breaking news: Java 21 released!");

        publisher.removeObserver(subscriber1);

        publisher.setNews("Another news: Spring Framework update.");
    }
}

Real-Life Use Case

A common real-life use case is in GUI frameworks where UI elements (Observers) need to be updated when the underlying data (Subject) changes. For example, a spreadsheet application uses the Observer pattern: when a cell's value changes, dependent charts and calculations are automatically updated.

When to use them

Use the Observer pattern when:

  • A change to one object requires changing other objects, and you don't know in advance how many objects need to be changed.
  • An object should be able to notify other objects without making assumptions about who these objects are. In other words, you don’t want these objects to be tightly coupled.
  • When an abstraction has two aspects, one dependent on the other. Encapsulating these aspects in separate objects lets you vary and reuse them independently.

Best Practices

  • Consider thread safety: If the Subject and Observers are running in different threads, you need to ensure that the notification mechanism is thread-safe.
  • Avoid cyclic dependencies: Be careful not to create cyclic dependencies between Subjects and Observers, as this can lead to infinite loops.
  • Use weak references: To avoid memory leaks, consider using weak references for the Observers, especially if the Subject has a longer lifespan than the Observers.

Alternatives

Alternatives to the Observer pattern include:

  • Publish-Subscribe: A more loosely coupled version of the Observer pattern, where messages are published to channels and subscribers receive messages from those channels. This adds an intermediary message broker.
  • Signals/Slots: Used in some GUI frameworks, similar to Observer but often uses function pointers or delegates for more direct invocation.

Pros

  • Loose Coupling: Subjects and observers are loosely coupled, promoting reusability and maintainability.
  • Flexibility: Easily add or remove observers at runtime.
  • Scalability: Supports a one-to-many relationship, allowing multiple observers to react to changes in the subject.

Cons

  • Unintended Updates: Observers may receive updates they are not interested in, leading to unnecessary processing.
  • Complexity: Can increase complexity if not implemented carefully, especially with complex notification logic.
  • Potential Memory Leaks: If observers are not properly unregistered, they can lead to memory leaks.

FAQ

  • What is the difference between Observer and Publish-Subscribe?

    The Observer pattern is typically implemented directly between subjects and observers, while Publish-Subscribe uses a message broker or event bus as an intermediary. Publish-Subscribe offers greater decoupling.
  • How can I avoid memory leaks in the Observer pattern?

    Use weak references to observers, or ensure that observers are properly unregistered from the subject when they are no longer needed.
  • Is the Observer pattern suitable for multithreaded environments?

    Yes, but you need to ensure thread safety by using synchronization mechanisms to protect the observer list and notification process.