Skip to content

Observer

Defines a one-to-many dependency relationship between objects, where when the state of one object changes, all objects depending on it are notified and automatically updated.

The Observer pattern, also known as the Publish-Subscribe pattern (Pub/Sub), is a notification mechanism that allows the notifying party (the observed) and the receiving party (the observers) to be separated without affecting each other.

To understand the Observer pattern, let's look at an example.

Imagine an e-commerce website that has various Products. Both Customers and Admins are interested in product listings and price changes and want to be notified immediately. The Store can be implemented like this:

java
public class Store {
    Customer customer;
    Admin admin;

    private Map<String, Product> products = new HashMap<>();

    public void addNewProduct(String name, double price) {
        Product p = new Product(name, price);
        products.put(p.getName(), p);
        // Notify customers:
        customer.onPublished(p);
        // Notify admins:
        admin.onPublished(p);
    }

    public void setProductPrice(String name, double price) {
        Product p = products.get(name);
        p.setPrice(price);
        // Notify customers:
        customer.onPriceChanged(p);
        // Notify admins:
        admin.onPriceChanged(p);
    }
}

The problem with the above Store class is that it directly references Customer and Admin. Even without considering multiple customers or admins, the main issue is that if a new observer type, such as a government regulator, needs to be added, the Store class would have to be modified again.

The essence of the problem is that the Store wants to notify those interested in Product changes without knowing who they are. The Observer pattern aims to decouple the observed from the observers.

To achieve this goal, the Store should not directly reference Customer and Admin. Instead, it should reference a ProductObserver interface. Anyone who wants to observe the Store can implement this interface and register themselves with the Store:

java
public class Store {
    private List<ProductObserver> observers = new ArrayList<>();
    private Map<String, Product> products = new HashMap<>();

    // Register an observer:
    public void addObserver(ProductObserver observer) {
        this.observers.add(observer);
    }

    // Unregister an observer:
    public void removeObserver(ProductObserver observer) {
        this.observers.remove(observer);
    }

    public void addNewProduct(String name, double price) {
        Product p = new Product(name, price);
        products.put(p.getName(), p);
        // Notify observers:
        observers.forEach(o -> o.onPublished(p));
    }

    public void setProductPrice(String name, double price) {
        Product p = products.get(name);
        p.setPrice(price);
        // Notify observers:
        observers.forEach(o -> o.onPriceChanged(p));
    }
}

This small change allows the types of observers to be expanded indefinitely, and the definition of observers can be placed on the client side:

java
// Observer:
Admin a = new Admin();
Customer c = new Customer();
// Store:
Store store = new Store();
// Register observers:
store.addObserver(a);
store.addObserver(c);

It is even possible to register anonymous observers:

java
store.addObserver(new ProductObserver() {
    public void onPublished(Product product) {
        System.out.println("[Log] on product published: " + product);
    }

    public void onPriceChanged(Product product) {
        System.out.println("[Log] on product price changed: " + product);
    }
});

An illustration of the Observer pattern:

┌─────────┐      ┌───────────────┐
│  Store  │ ─ ─ ▶│ProductObserver │
└─────────┘      └───────────────┘
     │                   ▲

     │             ┌─────┴─────┐
     ▼             │           │
┌─────────┐   ┌─────────┐ ┌─────────┐
│ Product │   │  Admin  │ │Customer │ ...
└─────────┘   └─────────┘ └─────────┘

There are many variations of the Observer pattern. Some implementations abstract the observed as an interface:

java
public interface ProductObservable { // Note: Observable, not Observer!
    void addObserver(ProductObserver observer);
    void removeObserver(ProductObserver observer);
}

The corresponding observed entity must implement this interface:

java
public class Store implements ProductObservable {
    ...
}

Some Observer patterns use an Event object for notifications, thereby unifying various notification methods into one:

java
public interface ProductObserver {
    void onEvent(ProductEvent event);
}

Observers can then read the notification type and data from the Event object.

Broadly speaking, the Observer pattern encompasses all messaging systems. A messaging system completely separates observers from the observed by using the messaging system itself to notify:

                 ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐
                   Messaging System
                 │                       │
                    ┌──────────────────┐
              ┌──┼─▶│Topic:newProduct  │─┼─┐   ┌─────────┐
              │     └──────────────────┘   ├──▶│ConsumerA│
┌─────────┐   │  │  ┌──────────────────┐ │ │   └─────────┘
│Producer │───┼────▶│Topic:priceChanged│───┘
└─────────┘   │  │  └──────────────────┘ │
              │     ┌──────────────────┐       ┌─────────┐
              └──┼─▶│Topic:soldOut     │─┼────▶│ConsumerB│
                    └──────────────────┘       └─────────┘
                 └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘

The message sender is called a Producer, and the message receiver is called a Consumer. When a Producer sends a message, it must choose which Topic to send it to. Consumers can subscribe to the Topics they are interested in to receive specific types of messages.

When using a messaging system to implement the Observer pattern, Producers and Consumers often reside on different machines, and they know nothing about each other, as the observer registration occurs within the messaging system, not inside the Producer.

Additionally, note that in our Observer pattern implementation, notifications to observers are synchronous:

java
observers.forEach(o -> o.onPublished(p));

This means each observer receives notifications sequentially. If one observer processes too slowly, the next observer cannot be notified promptly. Moreover, if an observer throws an exception during notification processing, the observed must handle the exception to continue notifying the next observer.

Consideration: How to Change to Asynchronous Notification to Allow All Observers to Process Concurrently?

Some may have noticed that the Java standard library contains a java.util.Observable class and an Observer interface to help implement the Observer pattern. However, these classes are not recommended due to poor usability.

Practice

Add a new type of observer to the Store and change notifications to be asynchronous.

Summary

The Observer pattern, also known as the Publish-Subscribe pattern, is a one-to-many notification mechanism that allows both sides to focus only on the notifications themselves, not on each other.

Observer has loaded