20 minlesson

Introduction to Observer Pattern

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:

typescript
1// Tightly coupled notification system
2class PackageTracker {
3 private status: string = 'pending';
4
5 updateStatus(newStatus: string): void {
6 this.status = newStatus;
7
8 // Hardcoded notification logic
9 this.sendEmail(`Package status changed to ${newStatus}`);
10 this.sendSMS(`Package is now ${newStatus}`);
11 this.sendPushNotification(`Status update: ${newStatus}`);
12 this.logToWebhook(newStatus);
13
14 // 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 }
18
19 private sendEmail(message: string): void {
20 console.log(`Email: ${message}`);
21 }
22
23 private sendSMS(message: string): void {
24 console.log(`SMS: ${message}`);
25 }
26
27 private sendPushNotification(message: string): void {
28 console.log(`Push: ${message}`);
29 }
30
31 private logToWebhook(status: string): void {
32 console.log(`Webhook: ${status}`);
33 }
34}
35
36// Problems:
37// - All notification logic is hardcoded
38// - Can't add/remove notifications dynamically
39// - Can't customize per customer
40// - Violates Single Responsibility Principle
41// - Testing is difficult

The Solution: Observer Pattern

Separate the subject (package tracker) from observers (notification handlers):

typescript
1// Subject interface
2interface Subject {
3 attach(observer: Observer): void;
4 detach(observer: Observer): void;
5 notify(): void;
6}
7
8// Observer interface
9interface Observer {
10 update(subject: Subject): void;
11}
12
13class PackageTracker implements Subject {
14 private observers: Observer[] = [];
15 private status: string = 'pending';
16
17 attach(observer: Observer): void {
18 this.observers.push(observer);
19 }
20
21 detach(observer: Observer): void {
22 const index = this.observers.indexOf(observer);
23 if (index > -1) {
24 this.observers.splice(index, 1);
25 }
26 }
27
28 notify(): void {
29 for (const observer of this.observers) {
30 observer.update(this);
31 }
32 }
33
34 updateStatus(newStatus: string): void {
35 this.status = newStatus;
36 this.notify(); // Notify all observers
37 }
38
39 getStatus(): string {
40 return this.status;
41 }
42}
43
44class EmailNotifier implements Observer {
45 update(subject: PackageTracker): void {
46 console.log(`Email: Package status is ${subject.getStatus()}`);
47 }
48}
49
50class SMSNotifier implements Observer {
51 update(subject: PackageTracker): void {
52 console.log(`SMS: Package is ${subject.getStatus()}`);
53 }
54}
55
56// Usage
57const tracker = new PackageTracker();
58tracker.attach(new EmailNotifier());
59tracker.attach(new SMSNotifier());
60
61tracker.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 │ notifies
13
14┌──────────────────────────────────────┐
15│ Observer │
16│──────────────────────────────────────│
17│ + update(subject: Subject): void │
18└──────────────────────────────────────┘
19
20 │ implements
21 ┌────┴────┬────────────┬─────────┐
22 │ │ │ │
23┌───┴───┐ ┌──┴───┐ ┌─────┴────┐ ┌──┴────┐
24│ Email │ │ SMS │ │ Push │ │ Webhook│
25│Notify │ │Notify│ │ Notify │ │ Notify │
26└───────┘ └──────┘ └──────────┘ └────────┘

Key Participants

  1. Subject (Observable) - Maintains list of observers and sends notifications
  2. Observer - Interface for objects that should be notified
  3. ConcreteSubject - Stores state, notifies observers on changes
  4. ConcreteObserver - Implements update interface to stay synchronized

When to Use Observer Pattern

  1. State change notifications - One object's change affects many others
  2. Loose coupling - Subject shouldn't know details about observers
  3. Dynamic subscriptions - Observers can subscribe/unsubscribe at runtime
  4. Event systems - Implementing event-driven architectures

Logistics Use Cases

SubjectObservers
Package TrackerEmail, SMS, Push, Webhook notifications
Inventory SystemReorder service, Analytics, Dashboard
Delivery RouteDriver app, Customer app, Dispatch system
Order StatusCustomer 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

ObserverPub/Sub
Subject knows observersPublisher doesn't know subscribers
Direct notificationEvent broker mediates
Same processCan be distributed
Tighter couplingLooser 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.