Observer Pattern

Introduction

The Observer Pattern establishes a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically. Think of it as a subscription model — interested parties register to receive updates, and the publisher broadcasts changes without knowing who is listening.

This pattern is the backbone of event-driven architectures. From GUI button clicks to microservice event buses, the Observer Pattern enables loose coupling between components that need to react to changes.

The Problem

You are building an e-commerce platform. When an order is placed, several things must happen: send a confirmation email, update inventory, notify the analytics service, and trigger the shipping workflow. The naive approach is to put all this logic directly in the placeOrder() method.

This creates tight coupling — the order service now depends on the email service, inventory service, analytics service, and shipping service. Every time you add a new reaction to “order placed,” you must modify the order class. Testing becomes painful because you cannot place an order without triggering every downstream system.

The Solution

The Observer Pattern decouples the event source (order service) from the event handlers (email, inventory, analytics). The order service maintains a list of observers and simply notifies them when an order is placed. Each observer decides independently how to react.

Adding a new reaction — say, a loyalty points service — requires only creating a new observer and registering it. The order service code remains untouched.

Key Principle

Strive for loosely coupled designs between objects that interact. The subject (publisher) knows nothing about its observers except that they implement a common interface. This means you can add, remove, or replace observers without modifying the subject.

Java Example

Scenario: An order event system that notifies multiple services when an order is placed.

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

// Event data
public class OrderEvent {
    private final String orderId;
    private final String customerEmail;
    private final double totalAmount;
    private final List<String> items;

    public OrderEvent(String orderId, String customerEmail,
                      double totalAmount, List<String> items) {
        this.orderId = orderId;
        this.customerEmail = customerEmail;
        this.totalAmount = totalAmount;
        this.items = items;
    }

    public String getOrderId() { return orderId; }
    public String getCustomerEmail() { return customerEmail; }
    public double getTotalAmount() { return totalAmount; }
    public List<String> getItems() { return items; }
}

// Observer interface
public interface OrderObserver {
    void onOrderPlaced(OrderEvent event);
}

// Subject (Publisher)
public class OrderService {
    private final List<OrderObserver> observers = new ArrayList<>();

    public void subscribe(OrderObserver observer) {
        observers.add(observer);
    }

    public void unsubscribe(OrderObserver observer) {
        observers.remove(observer);
    }

    public void placeOrder(String orderId, String email,
                           double total, List<String> items) {
        System.out.println("Order " + orderId + " placed successfully.");
        OrderEvent event = new OrderEvent(orderId, email, total, items);

        // Notify all observers
        for (OrderObserver observer : observers) {
            observer.onOrderPlaced(event);
        }
    }
}

// Concrete Observer: Email Notification
public class EmailNotificationObserver implements OrderObserver {
    @Override
    public void onOrderPlaced(OrderEvent event) {
        System.out.printf("[Email] Sending confirmation to %s for order %s ($%.2f)%n",
            event.getCustomerEmail(), event.getOrderId(), event.getTotalAmount());
    }
}

// Concrete Observer: Inventory Update
public class InventoryObserver implements OrderObserver {
    @Override
    public void onOrderPlaced(OrderEvent event) {
        System.out.printf("[Inventory] Reserving %d items for order %s%n",
            event.getItems().size(), event.getOrderId());
    }
}

// Concrete Observer: Analytics Tracking
public class AnalyticsObserver implements OrderObserver {
    @Override
    public void onOrderPlaced(OrderEvent event) {
        System.out.printf("[Analytics] Tracking order %s — revenue: $%.2f%n",
            event.getOrderId(), event.getTotalAmount());
    }
}

// Usage
public class Main {
    public static void main(String[] args) {
        OrderService orderService = new OrderService();

        // Register observers
        orderService.subscribe(new EmailNotificationObserver());
        orderService.subscribe(new InventoryObserver());
        orderService.subscribe(new AnalyticsObserver());

        // Place an order — all observers get notified
        orderService.placeOrder("ORD-1001", "jane@example.com", 149.99,
            List.of("Laptop Stand", "USB-C Hub"));
    }
}

Python Example

Same order notification system in Python.

from abc import ABC, abstractmethod
from dataclasses import dataclass


@dataclass
class OrderEvent:
    order_id: str
    customer_email: str
    total_amount: float
    items: list[str]


# Observer interface
class OrderObserver(ABC):
    @abstractmethod
    def on_order_placed(self, event: OrderEvent) -> None:
        pass


# Subject (Publisher)
class OrderService:
    def __init__(self):
        self._observers: list[OrderObserver] = []

    def subscribe(self, observer: OrderObserver) -> None:
        self._observers.append(observer)

    def unsubscribe(self, observer: OrderObserver) -> None:
        self._observers.remove(observer)

    def place_order(self, order_id: str, email: str,
                    total: float, items: list[str]) -> None:
        print(f"Order {order_id} placed successfully.")
        event = OrderEvent(order_id, email, total, items)

        # Notify all observers
        for observer in self._observers:
            observer.on_order_placed(event)


# Concrete Observer: Email Notification
class EmailNotificationObserver(OrderObserver):
    def on_order_placed(self, event: OrderEvent) -> None:
        print(f"[Email] Sending confirmation to {event.customer_email} "
              f"for order {event.order_id} (${event.total_amount:.2f})")


# Concrete Observer: Inventory Update
class InventoryObserver(OrderObserver):
    def on_order_placed(self, event: OrderEvent) -> None:
        print(f"[Inventory] Reserving {len(event.items)} items "
              f"for order {event.order_id}")


# Concrete Observer: Analytics Tracking
class AnalyticsObserver(OrderObserver):
    def on_order_placed(self, event: OrderEvent) -> None:
        print(f"[Analytics] Tracking order {event.order_id} "
              f"— revenue: ${event.total_amount:.2f}")


# Usage
if __name__ == "__main__":
    order_service = OrderService()

    # Register observers
    order_service.subscribe(EmailNotificationObserver())
    order_service.subscribe(InventoryObserver())
    order_service.subscribe(AnalyticsObserver())

    # Place an order — all observers get notified
    order_service.place_order(
        "ORD-1001", "jane@example.com", 149.99,
        ["Laptop Stand", "USB-C Hub"]
    )

When to Use

  • Event-driven systems — When one action should trigger multiple independent reactions (e.g., order placed, user signed up, file uploaded).
  • Decoupling modules — When the publisher should not know or care about who reacts to its events.
  • Dynamic subscriptions — When observers need to be added or removed at runtime (e.g., enabling/disabling notification channels).
  • Cross-cutting concerns — When logging, auditing, or metrics need to react to business events without polluting core logic.

Real-World Usage

  • Java Swing/AWT Event Listeners — GUI components use observers (ActionListener, MouseListener) to handle user interactions.
  • Spring ApplicationEvent — Spring’s event system lets beans publish and listen to application events using the observer pattern.
  • JavaScript DOM EventsaddEventListener() is the observer pattern — elements are subjects, handlers are observers.
  • Apache Kafka / RabbitMQ — Message broker pub/sub systems are distributed implementations of the observer pattern.



Subscribe To Our Newsletter
You will receive our latest post and tutorial.
Thank you for subscribing!

required
required


Leave a Reply

Your email address will not be published. Required fields are marked *