# Observer Pattern: Event-Driven Programming

# Observer Pattern: Event-Driven Programming

## Problem

In traditional programming, objects are tightly coupled. When one object needs to notify others about state changes, it must maintain direct references to all dependent objects. This creates several issues:

- **Tight Coupling**: Objects depend directly on each other, making code rigid
- **Scalability Issues**: Adding new observers requires modifying the subject class
- **Maintenance Burden**: Changes propagate through interconnected components
- **Code Reusability**: Components can't be reused independently
- **Runtime Flexibility**: Can't dynamically add/remove observers without code changes

**Example Problem**: A stock price system where multiple displays (digital, analog, mobile app) need updates whenever the price changes. Without proper decoupling, the stock class must know about every display type.

---

## Solution

The **Observer Pattern** implements event-driven architecture through publish-subscribe mechanism:

### Core Concepts

1. **Subject (Publisher)**: Maintains list of observers and notifies them of state changes
2. **Observer (Subscriber)**: Defines interface for receiving notifications
3. **Concrete Observer**: Implements specific reaction to notifications
4. **Concrete Subject**: Stores state and sends notifications

### Benefits

- **Loose Coupling**: Subject and observers interact through abstract interfaces
- **Dynamic Relationships**: Add/remove observers at runtime
- **Broadcast Communication**: One-to-many relationships handled elegantly
- **Separation of Concerns**: Each observer handles its own logic
- **Extensibility**: New observers added without modifying existing code

---

## Code Implementation

### JavaScript/TypeScript

```javascript
// Abstract Observer Interface
class Observer {
  update(data) {
    throw new Error('update() must be implemented');
  }
}

// Concrete Subject (Publisher)
class StockPrice {
  constructor() {
    this.observers = [];
    this._price = 0;
  }

  // Subscribe observer
  attach(observer) {
    if (!this.observers.includes(observer)) {
      this.observers.push(observer);
      console.log(`${observer.constructor.name} subscribed`);
    }
  }

  // Unsubscribe observer
  detach(observer) {
    const index = this.observers.indexOf(observer);
    if (index > -1) {
      this.observers.splice(index, 1);
      console.log(`${observer.constructor.name} unsubscribed`);
    }
  }

  // Notify all observers
  notify() {
    this.observers.forEach(observer => {
      observer.update(this._price);
    });
  }

  // Update state and trigger notifications
  set price(newPrice) {
    if (this._price !== newPrice) {
      this._price = newPrice;
      this.notify();
    }
  }

  get price() {
    return this._price;
  }
}

// Concrete Observer 1: Digital Display
class DigitalDisplay extends Observer {
  update(price) {
    console.log(`📱 Digital Display: $${price.toFixed(2)}`);
  }
}

// Concrete Observer 2: Analog Display
class AnalogDisplay extends Observer {
  update(price) {
    console.log(`🎯 Analog Display: Price at ${price}%`);
  }
}

// Concrete Observer 3: Mobile App
class MobileApp extends Observer {
  constructor(userId) {
    super();
    this.userId = userId;
  }

  update(price) {
    console.log(`📲 Mobile App (User ${this.userId}): Alert! New price: $${price}`);
  }
}

// Usage
const stock = new StockPrice();

const digital = new DigitalDisplay();
const analog = new AnalogDisplay();
const mobile = new MobileApp('user123');

// Subscribe observers
stock.attach(digital);
stock.attach(analog);
stock.attach(mobile);

console.log('\n--- Price Update: $100 ---');
stock.price = 100;

console.log('\n--- Price Update: $105.50 ---');
stock.price = 105.50;

console.log('\n--- Unsubscribe Analog Display ---');
stock.detach(analog);

console.log('\n--- Price Update: $110 ---');
stock.price = 110;
```

### Python

```python
from abc import ABC, abstractmethod
from typing import List

# Abstract Observer
class Observer(ABC):
    @abstractmethod
    def update(self, data):
        pass

# Concrete Subject (Publisher)
class StockPrice:
    def __init__(self):
        self._observers: List[Observer] = []
        self._price = 0

    def attach(self, observer: Observer):
        if observer not in self._observers:
            self._observers.append(observer)
            print(f"{observer.__class__.__name__} subscribed")

    def detach(self, observer: Observer):
        if observer in self._observers:
            self._observers.remove(observer)
            print(f"{observer.__class__.__name__} unsubscribed")

    def notify(self):
        for observer in self._observers:
            observer.update(self._price)

    @property
    def price(self):
        return self._price

    @price.setter
    def price(self, new_price):
        if self._price != new_price:
            self._price = new_price
            self.notify()

# Concrete Observers
class DigitalDisplay(Observer):
    def update(self, price):
        print(f"📱 Digital Display: ${price:.2f}")

class AnalogDisplay(Observer):
    def update(self, price):
        print(f"🎯 Analog Display: Price at {price}%")

class MobileApp(Observer):
    def __init__(self, user_id):
        self.user_id = user_id

    def update(self, price):
        print(f"📲 Mobile App (User {self.user_id}): Alert! New price: ${price}")

# Usage
stock = StockPrice()

digital = DigitalDisplay()
analog = AnalogDisplay()
mobile = MobileApp("user123")

stock.attach(digital)
stock.attach(analog)
stock.attach(mobile)

print("\n--- Price Update: $100 ---")
stock.price = 100

print("\n--- Price Update: $105.50 ---")
stock.price = 105.50

print("\n--- Unsubscribe Analog Display ---")
stock.detach(analog)

print("\n--- Price Update: $110 ---")
stock.price = 110
```

### Java

```java
import java.util.*;

// Observer Interface
interface Observer {
    void update(double price);
}

// Subject (Publisher)
class StockPrice {
    private List<Observer> observers = new ArrayList<>();
    private double price;

    public void attach(Observer observer) {
        if (!observers.contains(observer)) {
            observers.add(observer);
            System.out.println(observer.getClass().getSimpleName() + " subscribed");
        }
    }

    public void detach(Observer observer) {
        if (observers.remove(observer)) {
            System.out.println(observer.getClass().getSimpleName() + " unsubscribed");
        }
    }

    private void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(price);
        }
    }

    public void setPrice(double newPrice) {
        if (this.price != newPrice) {
            this.price = newPrice;
            notifyObservers();
        }
    }

    public double getPrice() {
        return price;
    }
}

// Concrete Observers
class DigitalDisplay implements Observer {
    @Override
    public void update(double price) {
        System.out.println("📱 Digital Display: $" + String.format("%.2f", price));
    }
}

class MobileApp implements Observer {
    private String userId;

    public MobileApp(String userId) {
        this.userId = userId;
    }

    @Override
    public void update(double price) {
        System.out.println("📲 Mobile App (User " + userId + "): Alert! New price: $" + price);
    }
}

// Main
public class ObserverPatternDemo {
    public static void main(String[] args) {
        StockPrice stock = new StockPrice();

        Observer digital = new DigitalDisplay();
        Observer mobile = new MobileApp("user123");

        stock.attach(digital);
        stock.attach(mobile);

        System.out.println("\n--- Price Update: $100 ---");
        stock.setPrice(100);

        System.out.println("\n--- Price Update: $105.50 ---");
        stock.setPrice(105.50);
    }
}
```

---

## Real-World Applications

| Use Case | Example |
|----------|---------|
| **UI Frameworks** | React hooks, Vue watchers, Angular change detection |
| **Event Systems** | DOM events, keyboard/mouse listeners |
| **Message Queues** | RabbitMQ, Kafka pub-sub |
| **Real-time Updates** | WebSocket notifications, live feeds |
| **MVC Architecture** | Model notifies views of state changes |
| **Reactive Programming** | RxJS observables, reactive streams |

---

## Key Takeaways

✅ **Decouples** publishers from subscribers  
✅ **Enables** dynamic runtime relationships  
✅ **Supports** one-to-many communication  
✅ **Improves** code maintainability and testability  
✅ **Foundation** for event-driven architectures  

The Observer Pattern is fundamental to modern event-driven programming and reactive systems.
