• Shift Elevate
  • Posts
  • Observer Pattern: Build Reactive Systems with Loose Coupling

Observer Pattern: Build Reactive Systems with Loose Coupling

The Pain of Tightly Coupled Event Systems

Picture this: You're building a stock market monitoring application that needs to notify multiple components when stock prices change. Your initial approach seems logical: directly call update methods on each component from your stock price service.

But requirements evolve quickly. Every time you add a new component (email alerts, dashboard updates), you're modifying the stock service code. Your updateStockPrice() method becomes a tangled mess of direct method calls, creating tight coupling that makes testing impossible and maintenance a nightmare.

Suddenly, you're dealing with circular dependencies, broken notifications, and a system that breaks every time you add a new feature. Sound familiar?

The Observer pattern solves this challenge by establishing a one to many dependency between objects, allowing multiple observers to be notified automatically when a subject's state changes.

Understanding the Observer Pattern

The Observer pattern defines a one to many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically. It promotes loose coupling by allowing objects to interact without being tightly bound to each other.

Think of it like a news subscription system: subscribers (observers) register to receive updates from a news agency (subject). When news breaks, the agency automatically notifies all subscribers without knowing who they are or how they process the information.

This pattern promotes Loose Coupling, Extensibility, and Maintainability while enabling reactive programming paradigms.

๐Ÿ” Subject - Observer Relationship: The Heart of Observer

The Observer pattern's power comes from understanding the relationship between subjects and observers:

Subject (Publisher)

  • Definition: The object that maintains a list of observers and notifies them of state changes

  • Characteristics:

    • Maintains observer registration and removal

    • Notifies all observers when state changes

    • Independent of observer implementations

    • Can have multiple observers

  • Stock Market Example: StockPriceService that tracks price changes

  • Key Benefit: Centralized state management with automatic notifications

Observer (Subscriber)

  • Definition: Objects that want to be notified of changes in the subject's state

  • Characteristics:

    • Implements a common update interface

    • Registers with subjects of interest

    • Responds to notifications automatically

    • Can observe multiple subjects

  • Stock Market Example: EmailAlert, Dashboard

  • Key Benefit: Reactive behaviour without tight coupling

Observer Pattern Components

Core Components

  • Observer Interface: Defines the update interface for objects that should be notified of changes.

  • Subject Interface: Defines the interface for registering, removing, and notifying observers.

  • Concrete Observer: Implements the observer interface and defines how to respond to notifications (EmailAlert, Dashboard).

  • Concrete Subject: Implements the subject interface and maintains a list of observers (StockPriceService).

  • Client: Creates subjects and observers, and establishes the relationships between them.

Complete Java Implementation

Let's build a stock market monitoring system that demonstrates the Observer pattern's power in creating reactive, loosely coupled systems.

Observer Interface

public interface StockObserver {
    void update(String stockSymbol, double price, double change);
    String getObserverName();
}

Subject Interface

public interface StockSubject {
    void registerObserver(StockObserver observer);
    void removeObserver(StockObserver observer);
    void notifyObservers();
}

Concrete Subject

public class StockPriceService implements StockSubject {
    private List<StockObserver> observers = new ArrayList<>();
    private String stockSymbol;
    private double currentPrice;
    
    public StockPriceService(String stockSymbol, double initialPrice) {
        this.stockSymbol = stockSymbol;
        this.currentPrice = initialPrice;
    }
    
    @Override
    public void registerObserver(StockObserver observer) {
        observers.add(observer);
    }
    
    @Override
    public void removeObserver(StockObserver observer) {
        observers.remove(observer);
    }
    
    @Override
    public void notifyObservers() {
        for (StockObserver observer : observers) {
            observer.update(stockSymbol, currentPrice, 0);
        }
    }
    
    public void updatePrice(double newPrice) {
        this.currentPrice = newPrice;
        notifyObservers();
    }
}

Concrete Observers

public class EmailAlert implements StockObserver {
    private String email;
    
    public EmailAlert(String email) {
        this.email = email;
    }
    
    @Override
    public void update(String stockSymbol, double price, double change) {
        System.out.println("๐Ÿ“ง Email to " + email + ": " + 
            stockSymbol + " is now $" + price);
    }
    
    @Override
    public String getObserverName() {
        return "Email Alert";
    }
}

public class Dashboard implements StockObserver {
    private String name;
    
    public Dashboard(String name) {
        this.name = name;
    }
    
    @Override
    public void update(String stockSymbol, double price, double change) {
        System.out.println("๐Ÿ“Š " + name + ": " + 
            stockSymbol + " = $" + price);
    }
    
    @Override
    public String getObserverName() {
        return "Dashboard";
    }
}

Client

public class StockMarketDemo {
    public static void main(String[] args) {
        StockPriceService techStock = new StockPriceService("TECH", 150.00);
        
        EmailAlert emailAlert = new EmailAlert("[email protected]");
        Dashboard mainDashboard = new Dashboard("Main Dashboard");
        
        techStock.registerObserver(emailAlert);
        techStock.registerObserver(mainDashboard);
        
        techStock.updatePrice(155.50);
        
        techStock.removeObserver(emailAlert);
        techStock.updatePrice(160.00);
    }
}

Expected Output:

๐Ÿ“ง Email to [email protected]: TECH is now $155.5
๐Ÿ“Š Main Dashboard: TECH = $155.5

๐Ÿ“Š Main Dashboard: TECH = $160.0

๐Ÿš€ Get the Complete Implementation

The full code with advanced observer implementations and event handling is available in our Design Patterns Repository.

# Clone and run the complete demo
git clone https://github.com/shift-elevate/design-patterns.git
cd design-patterns
mvn test -Dtest=ObserverPatternTest

Advanced Observer Applications

The Observer pattern enables powerful reactive programming in various domains:

Event Driven Architecture

public interface EventObserver {
    void handleEvent(String eventType, String eventData);
}

public class EventBus {
    private List<EventObserver> observers = new ArrayList<>();
    
    public void subscribe(EventObserver observer) {
        observers.add(observer);
    }
    
    public void publish(String eventType, String eventData) {
        for (EventObserver observer : observers) {
            observer.handleEvent(eventType, eventData);
        }
    }
}

Model View Controller (MVC) Architecture

public class Model {
    private List<ViewObserver> views = new ArrayList<>();
    private String data;
    
    public void addView(ViewObserver view) {
        views.add(view);
    }
    
    public void setData(String newData) {
        this.data = newData;
        notifyViews();
    }
    
    private void notifyViews() {
        for (ViewObserver view : views) {
            view.update(data);
        }
    }
}

Real World Examples

The Observer pattern is extensively used in real world applications:

1. Reactive Programming

Frameworks like RxJava, React, and Angular use observer patterns extensively for handling asynchronous data streams and state management.

2. Microservices Architecture

Event driven microservices use observer patterns for service to service communication, where services publish events that other services can subscribe to.

When to Use Observer Pattern

Understanding when to apply the Observer pattern is crucial for making the right architectural decisions. Here's when it shines and when alternatives might be better:

โœ… Ideal Scenarios:

  • You need to notify multiple objects about changes to another object.

  • The coupling between objects should be loose and flexible.

  • You're building event driven or reactive systems.

  • You need to implement publish subscribe communication patterns.

โŒ Skip It When:

  • The number of observers is small and unlikely to change.

  • The update frequency is very high and performance is critical.

  • You need to maintain a specific order of notifications.

Next Steps: Apply Observer Pattern in Your Project

Ready to implement the Observer pattern in your own projects? Here's a structured approach to get you started:

  1. Identify Change Sources: Look for objects whose state changes need to be communicated to other objects.

  2. Define Observer Interface: Create a common interface for all objects that need to be notified.

  3. Implement Subject Management: Build registration, removal, and notification mechanisms.

  4. Create Concrete Observers: Implement specific observer classes for different notification needs.

  5. Test Loose Coupling: Ensure observers can be added and removed without modifying subject code.

The Observer pattern transforms tightly coupled systems into flexible, reactive architectures. By establishing loose coupling between subjects and observers, you build systems that can easily adapt to changing requirements while maintaining clean, testable code.

Found this helpful? Share it with a colleague who's struggling with tightly coupled event systems. Have questions about implementing Observer pattern in your specific use case? Email us directly, we read every message and the best questions become future newsletter topics.