Skip to content
intermediate Phase 6 · TypeScript Design Patterns

Observer & Strategy Patterns

Implement behavioral patterns for event handling and algorithm selection.

1h
0 problems
Topic Progress 0%

Observer Pattern

Observer Pattern

Typed Event Emitter

type EventMap = {
  'user:login': { userId: string; timestamp: Date };
  'user:logout': { userId: string };
  'data:sync': { records: number };
};

class TypedEventEmitter<Events extends Record<string, unknown>> {
  private listeners = new Map<keyof Events, Set<(data: any) => void>>();

  on<K extends keyof Events>(event: K, listener: (data: Events[K]) => void): () => void {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, new Set());
    }
    this.listeners.get(event)!.add(listener);

    // Return unsubscribe function
    return () => {
      this.listeners.get(event)?.delete(listener);
    };
  }

  emit<K extends keyof Events>(event: K, data: Events[K]): void {
    this.listeners.get(event)?.forEach(listener => listener(data));
  }
}

// Usage
const emitter = new TypedEventEmitter<EventMap>();

const unsubscribe = emitter.on('user:login', (data) => {
  console.log(data.userId);   // typed as string
  console.log(data.timestamp); // typed as Date
});

emitter.emit('user:login', { userId: '123', timestamp: new Date() });
unsubscribe();

Real-World Observer

interface Subscriber<T> {
  update(data: T): void;
}

class Observable<T> {
  private subscribers: Subscriber<T>[] = [];

  subscribe(subscriber: Subscriber<T>): () => void {
    this.subscribers.push(subscriber);
    return () => {
      this.subscribers = this.subscribers.filter(s => s !== subscriber);
    };
  }

  notify(data: T): void {
    this.subscribers.forEach(sub => sub.update(data));
  }
}

Strategy Pattern

Strategy Pattern

Sort Strategies

interface SortStrategy<T> {
  sort(data: T[], compareFn: (a: T, b: T) => number): T[];
}

class BubbleSort<T> implements SortStrategy<T> {
  sort(data: T[], compareFn: (a: T, b: T) => number): T[] {
    const arr = [...data];
    for (let i = 0; i < arr.length; i++) {
      for (let j = 0; j < arr.length - i - 1; j++) {
        if (compareFn(arr[j], arr[j + 1]) > 0) {
          [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
        }
      }
    }
    return arr;
  }
}

class QuickSort<T> implements SortStrategy<T> {
  sort(data: T[], compareFn: (a: T, b: T) => number): T[] {
    if (data.length <= 1) return data;
    const pivot = data[0];
    const left = data.slice(1).filter(x => compareFn(x, pivot) <= 0);
    const right = data.slice(1).filter(x => compareFn(x, pivot) > 0);
    return [...this.sort(left, compareFn), pivot, ...this.sort(right, compareFn)];
  }
}

class Sorter<T> {
  constructor(private strategy: SortStrategy<T>) {}

  setStrategy(strategy: SortStrategy<T>): void {
    this.strategy = strategy;
  }

  sort(data: T[], compareFn: (a: T, b: T) => number): T[] {
    return this.strategy.sort(data, compareFn);
  }
}

const sorter = new Sorter(new QuickSort<number>());
sorter.sort([3, 1, 4, 1, 5], (a, b) => a - b);

Command Pattern

Command Pattern

Basic Command

interface Command {
  execute(): void;
  undo(): void;
}

class InsertTextCommand implements Command {
  private position: number;

  constructor(
    private document: string[],
    private text: string
  ) {
    this.position = document.length;
  }

  execute(): void {
    this.document.push(this.text);
  }

  undo(): void {
    this.document.pop();
  }
}

class CommandHistory {
  private history: Command[] = [];
  private undone: Command[] = [];

  execute(command: Command): void {
    command.execute();
    this.history.push(command);
    this.undone = [];
  }

  undo(): void {
    const command = this.history.pop();
    if (command) {
      command.undo();
      this.undone.push(command);
    }
  }

  redo(): void {
    const command = this.undone.pop();
    if (command) {
      command.execute();
      this.history.push(command);
    }
  }
}

Mediator Pattern

Mediator Pattern

Chat Room Mediator

interface Mediator {
  sendMessage(message: string, sender: Colleague): void;
  addColleague(colleague: Colleague): void;
}

class Colleague {
  constructor(
    private mediator: Mediator,
    public name: string
  ) {
    mediator.addColleague(this);
  }

  send(message: string): void {
    this.mediator.sendMessage(message, this);
  }

  receive(message: string, sender: string): void {
    console.log(`${sender} to ${this.name}: ${message}`);
  }
}

class ChatRoom implements Mediator {
  private colleagues: Colleague[] = [];

  addColleague(colleague: Colleague): void {
    this.colleagues.push(colleague);
  }

  sendMessage(message: string, sender: Colleague): void {
    this.colleagues.forEach(colleague => {
      if (colleague !== sender) {
        colleague.receive(message, sender.name);
      }
    });
  }
}

// Usage
const chatRoom = new ChatRoom();
const alice = new Colleague(chatRoom, 'Alice');
const bob = new Colleague(chatRoom, 'Bob');
alice.send('Hello everyone!');