Gang of Four Design Patterns in TypeScript
Design patterns are proven solutions to recurring software design problems.
Factory Pattern
interface Button {
render(): string;
onClick(): void;
}
class WindowsButton implements Button {
render() { return '<button class="windows">Click</button>'; }
onClick() { console.log('Windows button clicked'); }
}
class MacButton implements Button {
render() { return '<button class="mac">Click</button>'; }
onClick() { console.log('Mac button clicked'); }
}
abstract class Dialog {
abstract createButton(): Button;
render() {
const button = this.createButton();
return button.render();
}
}
class WindowsDialog extends Dialog {
createButton() { return new WindowsButton(); }
}
class MacDialog extends Dialog {
createButton() { return new MacButton(); }
}
Observer Pattern
interface Observer<T> {
update(event: T): void;
}
class EventEmitter<T> {
private observers = new Map<string, Set<Observer<T>>>();
subscribe(event: string, observer: Observer<T>) {
if (!this.observers.has(event)) {
this.observers.set(event, new Set());
}
this.observers.get(event)!.add(observer);
}
unsubscribe(event: string, observer: Observer<T>) {
this.observers.get(event)?.delete(observer);
}
emit(event: string, data: T) {
this.observers.get(event)?.forEach(obs => obs.update(data));
}
}
// Usage
const emitter = new EventEmitter<{ userId: string }>();
emitter.subscribe('user:login', {
update: ({ userId }) => console.log(`User ${userId} logged in`),
});
Strategy Pattern
interface SortStrategy<T> {
sort(data: T[]): T[];
}
class QuickSort<T> implements SortStrategy<T> {
sort(data: T[]): T[] {
// Quick sort implementation
return [...data].sort();
}
}
class BubbleSort<T> implements SortStrategy<T> {
sort(data: T[]): T[] {
// Bubble sort implementation
return [...data].sort();
}
}
class Sorter<T> {
constructor(private strategy: SortStrategy<T>) {}
setStrategy(strategy: SortStrategy<T>) {
this.strategy = strategy;
}
sort(data: T[]): T[] {
return this.strategy.sort(data);
}
}
Decorator Pattern
interface Logger {
log(message: string): void;
}
class ConsoleLogger implements Logger {
log(message: string) { console.log(message); }
}
abstract class LoggerDecorator implements Logger {
constructor(protected logger: Logger) {}
abstract log(message: string): void;
}
class TimestampDecorator extends LoggerDecorator {
log(message: string) {
this.logger.log(`[${new Date().toISOString()}] ${message}`);
}
}
class PrefixDecorator extends LoggerDecorator {
constructor(logger: Logger, private prefix: string) { super(logger); }
log(message: string) {
this.logger.log(`${this.prefix}: ${message}`);
}
}
// Usage
const logger = new PrefixDecorator(
new TimestampDecorator(new ConsoleLogger()),
'APP'
);
logger.log('Server started');
// APP: [2025-01-01T00:00:00.000Z] Server started
Command Pattern
interface Command {
execute(): void;
undo(): void;
}
class TextEditor {
private content = '';
private history: Command[] = [];
executeCommand(command: Command) {
command.execute();
this.history.push(command);
}
undo() {
const command = this.history.pop();
command?.undo();
}
getContent() { return this.content; }
setContent(c: string) { this.content = c; }
}
class InsertTextCommand implements Command {
private previousContent: string;
constructor(private editor: TextEditor, private text: string) {
this.previousContent = editor.getContent();
}
execute() {
this.editor.setContent(this.editor.getContent() + this.text);
}
undo() {
this.editor.setContent(this.previousContent);
}
}
These patterns solve real architectural challenges encountered in professional software development.