Introduction to 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.
The Problem
Tracking package status changes requires multiple systems to stay synchronized:
typescript1// Tightly coupled notification system2class PackageTracker {3 private status: string = 'pending';45 updateStatus(newStatus: string): void {6 this.status = newStatus;78 // Hardcoded notification logic9 this.sendEmail(`Package status changed to ${newStatus}`);10 this.sendSMS(`Package is now ${newStatus}`);11 this.sendPushNotification(`Status update: ${newStatus}`);12 this.logToWebhook(newStatus);1314 // What if we need to add another notification method?15 // What if some customers don't want SMS?16 // What if we need different logic for different statuses?17 }1819 private sendEmail(message: string): void {20 console.log(`Email: ${message}`);21 }2223 private sendSMS(message: string): void {24 console.log(`SMS: ${message}`);25 }2627 private sendPushNotification(message: string): void {28 console.log(`Push: ${message}`);29 }3031 private logToWebhook(status: string): void {32 console.log(`Webhook: ${status}`);33 }34}3536// Problems:37// - All notification logic is hardcoded38// - Can't add/remove notifications dynamically39// - Can't customize per customer40// - Violates Single Responsibility Principle41// - Testing is difficult
The Solution: Observer Pattern
Separate the subject (package tracker) from observers (notification handlers):
typescript1// Subject interface2interface Subject {3 attach(observer: Observer): void;4 detach(observer: Observer): void;5 notify(): void;6}78// Observer interface9interface Observer {10 update(subject: Subject): void;11}1213class PackageTracker implements Subject {14 private observers: Observer[] = [];15 private status: string = 'pending';1617 attach(observer: Observer): void {18 this.observers.push(observer);19 }2021 detach(observer: Observer): void {22 const index = this.observers.indexOf(observer);23 if (index > -1) {24 this.observers.splice(index, 1);25 }26 }2728 notify(): void {29 for (const observer of this.observers) {30 observer.update(this);31 }32 }3334 updateStatus(newStatus: string): void {35 this.status = newStatus;36 this.notify(); // Notify all observers37 }3839 getStatus(): string {40 return this.status;41 }42}4344class EmailNotifier implements Observer {45 update(subject: PackageTracker): void {46 console.log(`Email: Package status is ${subject.getStatus()}`);47 }48}4950class SMSNotifier implements Observer {51 update(subject: PackageTracker): void {52 console.log(`SMS: Package is ${subject.getStatus()}`);53 }54}5556// Usage57const tracker = new PackageTracker();58tracker.attach(new EmailNotifier());59tracker.attach(new SMSNotifier());6061tracker.updateStatus('in-transit'); // Both notifiers receive update
Pattern Structure
1┌──────────────────────────────────────┐2│ Subject │3│ (Observable) │4│──────────────────────────────────────│5│ - observers: Observer[] │6│──────────────────────────────────────│7│ + attach(observer: Observer): void │8│ + detach(observer: Observer): void │9│ + notify(): void │10└──────────────────────────────────────┘11 │12 │ notifies13 ▼14┌──────────────────────────────────────┐15│ Observer │16│──────────────────────────────────────│17│ + update(subject: Subject): void │18└──────────────────────────────────────┘19 ▲20 │ implements21 ┌────┴────┬────────────┬─────────┐22 │ │ │ │23┌───┴───┐ ┌──┴───┐ ┌─────┴────┐ ┌──┴────┐24│ Email │ │ SMS │ │ Push │ │ Webhook│25│Notify │ │Notify│ │ Notify │ │ Notify │26└───────┘ └──────┘ └──────────┘ └────────┘
Key Participants
- Subject (Observable) - Maintains list of observers and sends notifications
- Observer - Interface for objects that should be notified
- ConcreteSubject - Stores state, notifies observers on changes
- ConcreteObserver - Implements update interface to stay synchronized
When to Use Observer Pattern
- State change notifications - One object's change affects many others
- Loose coupling - Subject shouldn't know details about observers
- Dynamic subscriptions - Observers can subscribe/unsubscribe at runtime
- Event systems - Implementing event-driven architectures
Logistics Use Cases
| Subject | Observers |
|---|---|
| Package Tracker | Email, SMS, Push, Webhook notifications |
| Inventory System | Reorder service, Analytics, Dashboard |
| Delivery Route | Driver app, Customer app, Dispatch system |
| Order Status | Customer notifications, Billing, Inventory |
Benefits
- Loose coupling - Subject and observers are independent
- Dynamic relationships - Add/remove observers at runtime
- Broadcast communication - One event notifies many listeners
- Open/Closed Principle - Add new observers without changing subject
Drawbacks
- Memory leaks - Observers not properly detached can prevent garbage collection
- Unexpected updates - Cascade of updates can be hard to track
- No guaranteed order - Observer notification order may be unpredictable
Observer vs Pub/Sub
| Observer | Pub/Sub |
|---|---|
| Subject knows observers | Publisher doesn't know subscribers |
| Direct notification | Event broker mediates |
| Same process | Can be distributed |
| Tighter coupling | Looser coupling |
Real-World Analogies
- Newsletter subscription - Readers subscribe to get updates when new content is published
- Stock market alerts - Traders get notified when stock prices change
- Social media followers - Followers get updates when you post
- Package tracking - Customers get notified of delivery status changes
Summary
The Observer pattern establishes a subscription mechanism to notify multiple objects about events happening to the object they're observing. It's fundamental to event-driven programming and reactive systems.