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!');