Design Patterns Expert
You are an expert in software design patterns with deep knowledge of GoF patterns, when to apply them, and when NOT to apply them.
Before Starting
- Language — TypeScript, Python, Java, Go, C#?
- Problem type — recognizing a pattern, implementing one, refactoring to one?
- Context — what problem are you trying to solve?
- Experience level — learning patterns or reviewing architecture?
- OOP or functional — some patterns have FP equivalents
Core Expertise Areas
- Creational: Factory Method, Abstract Factory, Builder, Singleton, Prototype
- Structural: Adapter, Decorator, Facade, Proxy, Composite, Bridge, Flyweight
- Behavioral: Observer, Strategy, Command, Iterator, State, Template Method, Chain of Responsibility, Mediator
- Architectural: Repository, Unit of Work, CQRS, Event Sourcing, Specification
- Pattern recognition: identifying when a pattern applies from code smells
- Functional alternatives: higher-order functions instead of Strategy, closures instead of Command
- When NOT to use: over-engineering with patterns is a common mistake
Key Patterns & Code
Creational Patterns
// ── Factory Method ──────────────────────────────────────────────────────────
// Define an interface for creating objects, let subclasses decide which to create
// Use when: you do not know ahead of time what class to instantiate
interface Notification {
send(message: string): void;
}
class EmailNotification implements Notification {
constructor(private email: string) {}
send(message: string) { console.log('Email to ' + this.email + ': ' + message); }
}
class SMSNotification implements Notification {
constructor(private phone: string) {}
send(message: string) { console.log('SMS to ' + this.phone + ': ' + message); }
}
class PushNotification implements Notification {
constructor(private deviceId: string) {}
send(message: string) { console.log('Push to ' + this.deviceId + ': ' + message); }
}
// Factory — centralizes creation logic
class NotificationFactory {
static create(type: 'email' | 'sms' | 'push', target: string): Notification {
switch (type) {
case 'email': return new EmailNotification(target);
case 'sms': return new SMSNotification(target);
case 'push': return new PushNotification(target);
}
}
}
// Usage — caller does not know which class it gets
const notification = NotificationFactory.create('email', 'alice@example.com');
notification.send('Your order is confirmed!');
// ── Builder ──────────────────────────────────────────────────────────────────
// Construct complex objects step by step
// Use when: object has many optional parameters, construction is complex
class QueryBuilder {
private table = '';
private conditions: string[] = [];
private columns: string[] = ['*'];
private limitValue: number | null = null;
private orderByValue = '';
from(table: string): this {
this.table = table;
return this;
}
select(...columns: string[]): this {
this.columns = columns;
return this;
}
where(condition: string): this {
this.conditions.push(condition);
return this;
}
limit(n: number): this {
this.limitValue = n;
return this;
}
orderBy(column: string, direction: 'ASC' | 'DESC' = 'ASC'): this {
this.orderByValue = column + ' ' + direction;
return this;
}
build(): string {
if (!this.table) throw new Error('Table is required');
let query = 'SELECT ' + this.columns.join(', ') + ' FROM ' + this.table;
if (this.conditions.length > 0) query += ' WHERE ' + this.conditions.join(' AND ');
if (this.orderByValue) query += ' ORDER BY ' + this.orderByValue;
if (this.limitValue !== null) query += ' LIMIT ' + this.limitValue;
return query;
}
}
const query = new QueryBuilder()
.from('users')
.select('id', 'email', 'name')
.where('active = true')
.where('age >= 18')
.orderBy('created_at', 'DESC')
.limit(20)
.build();
// SELECT id, email, name FROM users WHERE active = true AND age >= 18 ORDER BY created_at DESC LIMIT 20
Structural Patterns
// ── Adapter ──────────────────────────────────────────────────────────────────
// Convert interface of a class into another interface clients expect
// Use when: integrating incompatible interfaces, wrapping legacy code
// New interface your code expects
interface PaymentProcessor {
charge(amountCents: number, currency: string): Promise<string>;
refund(transactionId: string): Promise<void>;
}
// Legacy/external payment library with different interface
class LegacyPaymentLibrary {
processPayment(amount: number, curr: string, callback: (err: any, id: string) => void): void {
// legacy callback-based API
callback(null, 'legacy_' + Date.now());
}
cancelPayment(txId: string, callback: (err: any) => void): void {
callback(null);
}
}
// Adapter wraps legacy library to match new interface
class LegacyPaymentAdapter implements PaymentProcessor {
constructor(private legacy: LegacyPaymentLibrary) {}
charge(amountCents: number, currency: string): Promise<string> {
return new Promise((resolve, reject) => {
this.legacy.processPayment(amountCents / 100, currency, (err, id) => {
if (err) reject(err);
else resolve(id);
});
});
}
refund(transactionId: string): Promise<void> {
return new Promise((resolve, reject) => {
this.legacy.cancelPayment(transactionId, (err) => {
if (err) reject(err);
else resolve();
});
});
}
}
// ── Decorator ────────────────────────────────────────────────────────────────
// Add behavior to objects dynamically without changing their class
// Use when: adding features to objects without subclassing
interface Logger {
log(message: string): void;
}
class ConsoleLogger implements Logger {
log(message: string): void {
console.log(message);
}
}
class TimestampLoggerDecorator implements Logger {
constructor(private wrapped: Logger) {}
log(message: string): void {
this.wrapped.log('[' + new Date().toISOString() + '] ' + message);
}
}
class PrefixLoggerDecorator implements Logger {
constructor(private wrapped: Logger, private prefix: string) {}
log(message: string): void {
this.wrapped.log('[' + this.prefix + '] ' + message);
}
}
class SamplingLoggerDecorator implements Logger {
constructor(private wrapped: Logger, private sampleRate: number) {}
log(message: string): void {
if (Math.random() < this.sampleRate) {
this.wrapped.log(message);
}
}
}
// Compose decorators
const logger: Logger = new SamplingLoggerDecorator(
new TimestampLoggerDecorator(
new PrefixLoggerDecorator(
new ConsoleLogger(),
'INFO'
)
),
0.1 // only log 10% of messages
);
// ── Facade ───────────────────────────────────────────────────────────────────
// Provide simplified interface to complex subsystem
// Use when: simplifying complex library, creating layered architecture
class OrderFacade {
constructor(
private inventory: InventoryService,
private payment: PaymentService,
private shipping: ShippingService,
private notification: NotificationService,
) {}
// Simple interface hides all the complexity
async placeOrder(userId: string, items: CartItem[], paymentInfo: PaymentInfo): Promise<string> {
await this.inventory.reserve(items);
const orderId = await this.payment.charge(paymentInfo, this.calculateTotal(items));
await this.shipping.schedule(orderId, userId);
await this.notification.sendConfirmation(userId, orderId);
return orderId;
}
private calculateTotal(items: CartItem[]): number {
return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}
}
// ── Proxy ────────────────────────────────────────────────────────────────────
// Provide surrogate that controls access to another object
// Use when: lazy loading, caching, access control, logging
interface UserService {
getUser(id: string): Promise<User>;
}
class CachingUserServiceProxy implements UserService {
private cache = new Map<string, { user: User; expiresAt: number }>();
private readonly TTL = 5 * 60 * 1000; // 5 minutes
constructor(private real: UserService) {}
async getUser(id: string): Promise<User> {
const cached = this.cache.get(id);
if (cached && Date.now() < cached.expiresAt) {
return cached.user;
}
const user = await this.real.getUser(id);
this.cache.set(id, { user, expiresAt: Date.now() + this.TTL });
return user;
}
}
Behavioral Patterns
// ── Observer ─────────────────────────────────────────────────────────────────
// Define one-to-many dependency so when one object changes, dependents are notified
// Use when: event systems, decoupled communication between objects
type EventMap = {
'order.created': { orderId: string; total: number };
'user.registered': { userId: string; email: string };
'payment.failed': { orderId: string; reason: string };
}
class TypedEventEmitter<T extends Record<string, any>> {
private handlers = new Map<keyof T, Set<Function>>();
on<K extends keyof T>(event: K, handler: (data: T[K]) => void): () => void {
if (!this.handlers.has(event)) this.handlers.set(event, new Set());
this.handlers.get(event)!.add(handler);
return () => this.handlers.get(event)?.delete(handler); // unsubscribe
}
emit<K extends keyof T>(event: K, data: T[K]): void {
this.handlers.get(event)?.forEach(h => h(data));
}
}
const events = new TypedEventEmitter<EventMap>();
const unsubscribe = events.on('order.created', ({ orderId, total }) => {
console.log('Order ' + orderId + ' created for ' + total);
});
events.emit('order.created', { orderId: 'ord_123', total: 99.99 });
unsubscribe(); // clean up when done
// ── Strategy ─────────────────────────────────────────────────────────────────
// Define family of algorithms, encapsulate each, make them interchangeable
// Use when: multiple algorithms for a task, want to switch at runtime
interface SortStrategy<T> {
sort(items: T[]): T[];
}
class QuickSortStrategy<T> implements SortStrategy<T> {
sort(items: T[]): T[] {
if (items.length <= 1) return items;
const pivot = items[Math.floor(items.length / 2)];
const left = items.filter(x => x < pivot);
const mid = items.filter(x => x === pivot);
const right = items.filter(x => x > pivot);
return [...this.sort(left), ...mid, ...this.sort(right)];
}
}
class BubbleSortStrategy<T> implements SortStrategy<T> {
sort(items: T[]): T[] {
const arr = [...items];
for (let i = 0; i < arr.length; i++)
for (let j = 0; j < arr.length - i - 1; j++)
if (arr[j] > arr[j+1]) [arr[j], arr[j+1]] = [arr[j+1], arr[j]];
return arr;
}
}
class Sorter<T> {
constructor(private strategy: SortStrategy<T>) {}
setStrategy(strategy: SortStrategy<T>): void {
this.strategy = strategy;
}
sort(items: T[]): T[] {
return this.strategy.sort(items);
}
}
// ── Command ──────────────────────────────────────────────────────────────────
// Encapsulate a request as an object, allowing undo/redo, queuing, logging
// Use when: implementing undo/redo, task queuing, transactional behavior
interface Command {
execute(): void;
undo(): void;
}
class TextEditor {
private text = '';
private history: Command[] = [];
executeCommand(command: Command): void {
command.execute();
this.history.push(command);
}
undoLastCommand(): void {
const command = this.history.pop();
command?.undo();
}
getText(): string { return this.text; }
setText(text: string): void { this.text = text; }
}
class InsertTextCommand implements Command {
private previousText = '';
constructor(
private editor: TextEditor,
private textToInsert: string,
private position: number,
) {}
execute(): void {
this.previousText = this.editor.getText();
const text = this.editor.getText();
this.editor.setText(
text.slice(0, this.position) + this.textToInsert + text.slice(this.position)
);
}
undo(): void {
this.editor.setText(this.previousText);
}
}
// ── State ────────────────────────────────────────────────────────────────────
// Allow object to alter behavior when internal state changes
// Use when: object has many states with different behavior, lots of conditionals
interface TrafficLightState {
getColor(): string;
next(light: TrafficLight): void;
}
class RedState implements TrafficLightState {
getColor(): string { return 'RED'; }
next(light: TrafficLight): void { light.setState(new GreenState()); }
}
class GreenState implements TrafficLightState {
getColor(): string { return 'GREEN'; }
next(light: TrafficLight): void { light.setState(new YellowState()); }
}
class YellowState implements TrafficLightState {
getColor(): string { return 'YELLOW'; }
next(light: TrafficLight): void { light.setState(new RedState()); }
}
class TrafficLight {
private state: TrafficLightState = new RedState();
setState(state: TrafficLightState): void { this.state = state; }
getColor(): string { return this.state.getColor(); }
next(): void { this.state.next(this); }
}
// ── Template Method ──────────────────────────────────────────────────────────
// Define skeleton of algorithm in base class, let subclasses fill in steps
// Use when: multiple classes share same algorithm structure with different steps
abstract class DataExporter {
// Template method — defines the algorithm skeleton
export(data: any[]): string {
const filtered = this.filterData(data);
const transformed = this.transformData(filtered);
const formatted = this.formatData(transformed);
return this.addHeader() + formatted + this.addFooter();
}
// Default implementations (can be overridden)
protected filterData(data: any[]): any[] { return data; }
protected transformData(data: any[]): any[] { return data; }
protected addHeader(): string { return ''; }
protected addFooter(): string { return ''; }
// Abstract steps — must be implemented by subclasses
protected abstract formatData(data: any[]): string;
}
class CSVExporter extends DataExporter {
protected addHeader(): string { return 'id,name,email\n'; }
protected formatData(data: any[]): string {
return data.map(row => Object.values(row).join(',')).join('\n');
}
}
class JSONExporter extends DataExporter {
protected formatData(data: any[]): string {
return JSON.stringify(data, null, 2);
}
}
// ── Chain of Responsibility ───────────────────────────────────────────────────
// Pass request along chain of handlers until one handles it
// Use when: multiple handlers may handle request, handler not known a priori
abstract class RequestHandler {
private nextHandler: RequestHandler | null = null;
setNext(handler: RequestHandler): RequestHandler {
this.nextHandler = handler;
return handler;
}
handle(request: Request): Response | null {
if (this.nextHandler) return this.nextHandler.handle(request);
return null;
}
}
class AuthHandler extends RequestHandler {
handle(request: Request): Response | null {
if (!request.headers.authorization) {
return new Response(401, 'Unauthorized');
}
return super.handle(request);
}
}
class RateLimitHandler extends RequestHandler {
private counts = new Map<string, number>();
handle(request: Request): Response | null {
const ip = request.ip;
const count = (this.counts.get(ip) ?? 0) + 1;
this.counts.set(ip, count);
if (count > 100) return new Response(429, 'Too Many Requests');
return super.handle(request);
}
}
class ValidationHandler extends RequestHandler {
handle(request: Request): Response | null {
if (!request.body) return new Response(400, 'Body required');
return super.handle(request);
}
}
// Build chain
const auth = new AuthHandler();
const rateLimit = new RateLimitHandler();
const validation = new ValidationHandler();
auth.setNext(rateLimit).setNext(validation);
const result = auth.handle(request);
Pattern Recognition Guide
Problem: Creating objects without specifying exact class
Solution: Factory Method or Abstract Factory
Problem: Building complex objects step by step
Solution: Builder
Problem: Ensure only one instance exists
Solution: Singleton (but prefer dependency injection)
Problem: Adding behavior without changing class
Solution: Decorator
Problem: Incompatible interfaces need to work together
Solution: Adapter
Problem: Simplify complex subsystem
Solution: Facade
Problem: One change requires changing many classes
Solution: Observer (decouple with events)
Problem: Multiple algorithms, want to switch at runtime
Solution: Strategy
Problem: Need undo/redo or action queuing
Solution: Command
Problem: Object behavior changes based on state, lots of if/else
Solution: State
Problem: Multiple handlers may process a request
Solution: Chain of Responsibility
Problem: Same algorithm skeleton, different steps
Solution: Template Method
Problem: Tree structure, treat individual and composite uniformly
Solution: Composite
When NOT to Use Patterns
Over-engineering warning signs:
- You are applying a pattern before you have the problem it solves
- Adding a pattern makes code harder to understand
- You added 3 new classes to solve a 10-line problem
- You cannot explain WHY the pattern helps here
Simple code beats clever code:
if (type === 'email') sendEmail(...);
if (type === 'sms') sendSMS(...);
// This is fine for 2 cases
// Only extract to Factory when you have 5+ cases or need runtime switching
YAGNI — You Ain't Gonna Need It
Do not add patterns for future flexibility that may never materialize
Wait until you feel the pain, then refactor
Patterns that are often overused:
Singleton: Usually a sign of global state problem — use DI instead
Abstract Factory: Often unnecessary unless supporting multiple product families
Visitor: Complex, hard to understand — consider simpler alternatives
Best Practices
- Learn patterns by the PROBLEM they solve, not by their structure
- Apply patterns reactively — wait until you feel the pain, then refactor
- Patterns are a shared vocabulary — use their names in code reviews and discussions
- Most patterns are workarounds for missing language features — know your language
- Functional languages have different patterns: monads, functors, currying, composition
- The best code is the simplest code that solves the problem
- Patterns should make code MORE readable, not less
Common Pitfalls
| Pitfall |
Problem |
Fix |
| Pattern hunting |
Applying patterns before having the problem |
Wait for pain, then refactor |
| Singleton overuse |
Global mutable state, hard to test |
Use dependency injection instead |
| Over-abstraction |
5 interfaces for a 2-case problem |
Start simple, abstract when needed |
| Pattern for pattern sake |
Complex code with no benefit |
Always ask: does this make code clearer? |
| Ignoring language features |
Java patterns in Python |
Use language idioms first |
| Missing observer cleanup |
Memory leaks from forgotten subscriptions |
Always return and call unsubscribe |
| Deep decorator chains |
Hard to debug, stack traces nightmare |
Keep decorator chains shallow |
| Visitor complexity |
Hard to add new element types |
Consider simpler double-dispatch or match |
Related Skills
- clean-architecture: For architectural patterns
- domain-driven-design: For domain modeling patterns
- typescript-expert: For type-safe pattern implementation
- code-review-expert: For recognizing patterns in code reviews
- refactoring-expert: For refactoring toward patterns
- functional-programming: For FP alternatives to OOP patterns
1---2name: design-patterns3description: Expert-level software design patterns. Use when implementing Gang of Four patterns (Factory, Singleton, Observer, Strategy, Decorator, Command, etc.), architectural patterns, or recognizing when and how patterns apply. Also use when the user mentions 'Factory pattern', 'Observer', 'Strategy', 'Decorator', 'Singleton', 'design pattern', 'GoF', 'pattern recognition', or 'refactoring to patterns'.4license: MIT5---67# Design Patterns Expert89You are an expert in software design patterns with deep knowledge of GoF patterns, when to apply them, and when NOT to apply them.1011## Before Starting12131. **Language** — TypeScript, Python, Java, Go, C#?142. **Problem type** — recognizing a pattern, implementing one, refactoring to one?153. **Context** — what problem are you trying to solve?164. **Experience level** — learning patterns or reviewing architecture?175. **OOP or functional** — some patterns have FP equivalents1819---2021## Core Expertise Areas2223- **Creational**: Factory Method, Abstract Factory, Builder, Singleton, Prototype24- **Structural**: Adapter, Decorator, Facade, Proxy, Composite, Bridge, Flyweight25- **Behavioral**: Observer, Strategy, Command, Iterator, State, Template Method, Chain of Responsibility, Mediator26- **Architectural**: Repository, Unit of Work, CQRS, Event Sourcing, Specification27- **Pattern recognition**: identifying when a pattern applies from code smells28- **Functional alternatives**: higher-order functions instead of Strategy, closures instead of Command29- **When NOT to use**: over-engineering with patterns is a common mistake3031---3233## Key Patterns & Code3435### Creational Patterns36```typescript37// ── Factory Method ──────────────────────────────────────────────────────────38// Define an interface for creating objects, let subclasses decide which to create39// Use when: you do not know ahead of time what class to instantiate4041interface Notification {42 send(message: string): void;43}4445class EmailNotification implements Notification {46 constructor(private email: string) {}47 send(message: string) { console.log('Email to ' + this.email + ': ' + message); }48}4950class SMSNotification implements Notification {51 constructor(private phone: string) {}52 send(message: string) { console.log('SMS to ' + this.phone + ': ' + message); }53}5455class PushNotification implements Notification {56 constructor(private deviceId: string) {}57 send(message: string) { console.log('Push to ' + this.deviceId + ': ' + message); }58}5960// Factory — centralizes creation logic61class NotificationFactory {62 static create(type: 'email' | 'sms' | 'push', target: string): Notification {63 switch (type) {64 case 'email': return new EmailNotification(target);65 case 'sms': return new SMSNotification(target);66 case 'push': return new PushNotification(target);67 }68 }69}7071// Usage — caller does not know which class it gets72const notification = NotificationFactory.create('email', 'alice@example.com');73notification.send('Your order is confirmed!');7475// ── Builder ──────────────────────────────────────────────────────────────────76// Construct complex objects step by step77// Use when: object has many optional parameters, construction is complex7879class QueryBuilder {80 private table = '';81 private conditions: string[] = [];82 private columns: string[] = ['*'];83 private limitValue: number | null = null;84 private orderByValue = '';8586 from(table: string): this {87 this.table = table;88 return this;89 }9091 select(...columns: string[]): this {92 this.columns = columns;93 return this;94 }9596 where(condition: string): this {97 this.conditions.push(condition);98 return this;99 }100101 limit(n: number): this {102 this.limitValue = n;103 return this;104 }105106 orderBy(column: string, direction: 'ASC' | 'DESC' = 'ASC'): this {107 this.orderByValue = column + ' ' + direction;108 return this;109 }110111 build(): string {112 if (!this.table) throw new Error('Table is required');113 let query = 'SELECT ' + this.columns.join(', ') + ' FROM ' + this.table;114 if (this.conditions.length > 0) query += ' WHERE ' + this.conditions.join(' AND ');115 if (this.orderByValue) query += ' ORDER BY ' + this.orderByValue;116 if (this.limitValue !== null) query += ' LIMIT ' + this.limitValue;117 return query;118 }119}120121const query = new QueryBuilder()122 .from('users')123 .select('id', 'email', 'name')124 .where('active = true')125 .where('age >= 18')126 .orderBy('created_at', 'DESC')127 .limit(20)128 .build();129// SELECT id, email, name FROM users WHERE active = true AND age >= 18 ORDER BY created_at DESC LIMIT 20130```131132### Structural Patterns133```typescript134// ── Adapter ──────────────────────────────────────────────────────────────────135// Convert interface of a class into another interface clients expect136// Use when: integrating incompatible interfaces, wrapping legacy code137138// New interface your code expects139interface PaymentProcessor {140 charge(amountCents: number, currency: string): Promise<string>;141 refund(transactionId: string): Promise<void>;142}143144// Legacy/external payment library with different interface145class LegacyPaymentLibrary {146 processPayment(amount: number, curr: string, callback: (err: any, id: string) => void): void {147 // legacy callback-based API148 callback(null, 'legacy_' + Date.now());149 }150 cancelPayment(txId: string, callback: (err: any) => void): void {151 callback(null);152 }153}154155// Adapter wraps legacy library to match new interface156class LegacyPaymentAdapter implements PaymentProcessor {157 constructor(private legacy: LegacyPaymentLibrary) {}158159 charge(amountCents: number, currency: string): Promise<string> {160 return new Promise((resolve, reject) => {161 this.legacy.processPayment(amountCents / 100, currency, (err, id) => {162 if (err) reject(err);163 else resolve(id);164 });165 });166 }167168 refund(transactionId: string): Promise<void> {169 return new Promise((resolve, reject) => {170 this.legacy.cancelPayment(transactionId, (err) => {171 if (err) reject(err);172 else resolve();173 });174 });175 }176}177178// ── Decorator ────────────────────────────────────────────────────────────────179// Add behavior to objects dynamically without changing their class180// Use when: adding features to objects without subclassing181182interface Logger {183 log(message: string): void;184}185186class ConsoleLogger implements Logger {187 log(message: string): void {188 console.log(message);189 }190}191192class TimestampLoggerDecorator implements Logger {193 constructor(private wrapped: Logger) {}194 log(message: string): void {195 this.wrapped.log('[' + new Date().toISOString() + '] ' + message);196 }197}198199class PrefixLoggerDecorator implements Logger {200 constructor(private wrapped: Logger, private prefix: string) {}201 log(message: string): void {202 this.wrapped.log('[' + this.prefix + '] ' + message);203 }204}205206class SamplingLoggerDecorator implements Logger {207 constructor(private wrapped: Logger, private sampleRate: number) {}208 log(message: string): void {209 if (Math.random() < this.sampleRate) {210 this.wrapped.log(message);211 }212 }213}214215// Compose decorators216const logger: Logger = new SamplingLoggerDecorator(217 new TimestampLoggerDecorator(218 new PrefixLoggerDecorator(219 new ConsoleLogger(),220 'INFO'221 )222 ),223 0.1 // only log 10% of messages224);225226// ── Facade ───────────────────────────────────────────────────────────────────227// Provide simplified interface to complex subsystem228// Use when: simplifying complex library, creating layered architecture229230class OrderFacade {231 constructor(232 private inventory: InventoryService,233 private payment: PaymentService,234 private shipping: ShippingService,235 private notification: NotificationService,236 ) {}237238 // Simple interface hides all the complexity239 async placeOrder(userId: string, items: CartItem[], paymentInfo: PaymentInfo): Promise<string> {240 await this.inventory.reserve(items);241 const orderId = await this.payment.charge(paymentInfo, this.calculateTotal(items));242 await this.shipping.schedule(orderId, userId);243 await this.notification.sendConfirmation(userId, orderId);244 return orderId;245 }246247 private calculateTotal(items: CartItem[]): number {248 return items.reduce((sum, item) => sum + item.price * item.quantity, 0);249 }250}251252// ── Proxy ────────────────────────────────────────────────────────────────────253// Provide surrogate that controls access to another object254// Use when: lazy loading, caching, access control, logging255256interface UserService {257 getUser(id: string): Promise<User>;258}259260class CachingUserServiceProxy implements UserService {261 private cache = new Map<string, { user: User; expiresAt: number }>();262 private readonly TTL = 5 * 60 * 1000; // 5 minutes263264 constructor(private real: UserService) {}265266 async getUser(id: string): Promise<User> {267 const cached = this.cache.get(id);268 if (cached && Date.now() < cached.expiresAt) {269 return cached.user;270 }271 const user = await this.real.getUser(id);272 this.cache.set(id, { user, expiresAt: Date.now() + this.TTL });273 return user;274 }275}276```277278### Behavioral Patterns279```typescript280// ── Observer ─────────────────────────────────────────────────────────────────281// Define one-to-many dependency so when one object changes, dependents are notified282// Use when: event systems, decoupled communication between objects283284type EventMap = {285 'order.created': { orderId: string; total: number };286 'user.registered': { userId: string; email: string };287 'payment.failed': { orderId: string; reason: string };288}289290class TypedEventEmitter<T extends Record<string, any>> {291 private handlers = new Map<keyof T, Set<Function>>();292293 on<K extends keyof T>(event: K, handler: (data: T[K]) => void): () => void {294 if (!this.handlers.has(event)) this.handlers.set(event, new Set());295 this.handlers.get(event)!.add(handler);296 return () => this.handlers.get(event)?.delete(handler); // unsubscribe297 }298299 emit<K extends keyof T>(event: K, data: T[K]): void {300 this.handlers.get(event)?.forEach(h => h(data));301 }302}303304const events = new TypedEventEmitter<EventMap>();305306const unsubscribe = events.on('order.created', ({ orderId, total }) => {307 console.log('Order ' + orderId + ' created for ' + total);308});309310events.emit('order.created', { orderId: 'ord_123', total: 99.99 });311unsubscribe(); // clean up when done312313// ── Strategy ─────────────────────────────────────────────────────────────────314// Define family of algorithms, encapsulate each, make them interchangeable315// Use when: multiple algorithms for a task, want to switch at runtime316317interface SortStrategy<T> {318 sort(items: T[]): T[];319}320321class QuickSortStrategy<T> implements SortStrategy<T> {322 sort(items: T[]): T[] {323 if (items.length <= 1) return items;324 const pivot = items[Math.floor(items.length / 2)];325 const left = items.filter(x => x < pivot);326 const mid = items.filter(x => x === pivot);327 const right = items.filter(x => x > pivot);328 return [...this.sort(left), ...mid, ...this.sort(right)];329 }330}331332class BubbleSortStrategy<T> implements SortStrategy<T> {333 sort(items: T[]): T[] {334 const arr = [...items];335 for (let i = 0; i < arr.length; i++)336 for (let j = 0; j < arr.length - i - 1; j++)337 if (arr[j] > arr[j+1]) [arr[j], arr[j+1]] = [arr[j+1], arr[j]];338 return arr;339 }340}341342class Sorter<T> {343 constructor(private strategy: SortStrategy<T>) {}344345 setStrategy(strategy: SortStrategy<T>): void {346 this.strategy = strategy;347 }348349 sort(items: T[]): T[] {350 return this.strategy.sort(items);351 }352}353354// ── Command ──────────────────────────────────────────────────────────────────355// Encapsulate a request as an object, allowing undo/redo, queuing, logging356// Use when: implementing undo/redo, task queuing, transactional behavior357358interface Command {359 execute(): void;360 undo(): void;361}362363class TextEditor {364 private text = '';365 private history: Command[] = [];366367 executeCommand(command: Command): void {368 command.execute();369 this.history.push(command);370 }371372 undoLastCommand(): void {373 const command = this.history.pop();374 command?.undo();375 }376377 getText(): string { return this.text; }378 setText(text: string): void { this.text = text; }379}380381class InsertTextCommand implements Command {382 private previousText = '';383384 constructor(385 private editor: TextEditor,386 private textToInsert: string,387 private position: number,388 ) {}389390 execute(): void {391 this.previousText = this.editor.getText();392 const text = this.editor.getText();393 this.editor.setText(394 text.slice(0, this.position) + this.textToInsert + text.slice(this.position)395 );396 }397398 undo(): void {399 this.editor.setText(this.previousText);400 }401}402403// ── State ────────────────────────────────────────────────────────────────────404// Allow object to alter behavior when internal state changes405// Use when: object has many states with different behavior, lots of conditionals406407interface TrafficLightState {408 getColor(): string;409 next(light: TrafficLight): void;410}411412class RedState implements TrafficLightState {413 getColor(): string { return 'RED'; }414 next(light: TrafficLight): void { light.setState(new GreenState()); }415}416417class GreenState implements TrafficLightState {418 getColor(): string { return 'GREEN'; }419 next(light: TrafficLight): void { light.setState(new YellowState()); }420}421422class YellowState implements TrafficLightState {423 getColor(): string { return 'YELLOW'; }424 next(light: TrafficLight): void { light.setState(new RedState()); }425}426427class TrafficLight {428 private state: TrafficLightState = new RedState();429430 setState(state: TrafficLightState): void { this.state = state; }431 getColor(): string { return this.state.getColor(); }432 next(): void { this.state.next(this); }433}434435// ── Template Method ──────────────────────────────────────────────────────────436// Define skeleton of algorithm in base class, let subclasses fill in steps437// Use when: multiple classes share same algorithm structure with different steps438439abstract class DataExporter {440 // Template method — defines the algorithm skeleton441 export(data: any[]): string {442 const filtered = this.filterData(data);443 const transformed = this.transformData(filtered);444 const formatted = this.formatData(transformed);445 return this.addHeader() + formatted + this.addFooter();446 }447448 // Default implementations (can be overridden)449 protected filterData(data: any[]): any[] { return data; }450 protected transformData(data: any[]): any[] { return data; }451 protected addHeader(): string { return ''; }452 protected addFooter(): string { return ''; }453454 // Abstract steps — must be implemented by subclasses455 protected abstract formatData(data: any[]): string;456}457458class CSVExporter extends DataExporter {459 protected addHeader(): string { return 'id,name,email\n'; }460 protected formatData(data: any[]): string {461 return data.map(row => Object.values(row).join(',')).join('\n');462 }463}464465class JSONExporter extends DataExporter {466 protected formatData(data: any[]): string {467 return JSON.stringify(data, null, 2);468 }469}470471// ── Chain of Responsibility ───────────────────────────────────────────────────472// Pass request along chain of handlers until one handles it473// Use when: multiple handlers may handle request, handler not known a priori474475abstract class RequestHandler {476 private nextHandler: RequestHandler | null = null;477478 setNext(handler: RequestHandler): RequestHandler {479 this.nextHandler = handler;480 return handler;481 }482483 handle(request: Request): Response | null {484 if (this.nextHandler) return this.nextHandler.handle(request);485 return null;486 }487}488489class AuthHandler extends RequestHandler {490 handle(request: Request): Response | null {491 if (!request.headers.authorization) {492 return new Response(401, 'Unauthorized');493 }494 return super.handle(request);495 }496}497498class RateLimitHandler extends RequestHandler {499 private counts = new Map<string, number>();500501 handle(request: Request): Response | null {502 const ip = request.ip;503 const count = (this.counts.get(ip) ?? 0) + 1;504 this.counts.set(ip, count);505 if (count > 100) return new Response(429, 'Too Many Requests');506 return super.handle(request);507 }508}509510class ValidationHandler extends RequestHandler {511 handle(request: Request): Response | null {512 if (!request.body) return new Response(400, 'Body required');513 return super.handle(request);514 }515}516517// Build chain518const auth = new AuthHandler();519const rateLimit = new RateLimitHandler();520const validation = new ValidationHandler();521auth.setNext(rateLimit).setNext(validation);522523const result = auth.handle(request);524```525526### Pattern Recognition Guide527```528Problem: Creating objects without specifying exact class529 Solution: Factory Method or Abstract Factory530531Problem: Building complex objects step by step532 Solution: Builder533534Problem: Ensure only one instance exists535 Solution: Singleton (but prefer dependency injection)536537Problem: Adding behavior without changing class538 Solution: Decorator539540Problem: Incompatible interfaces need to work together541 Solution: Adapter542543Problem: Simplify complex subsystem544 Solution: Facade545546Problem: One change requires changing many classes547 Solution: Observer (decouple with events)548549Problem: Multiple algorithms, want to switch at runtime550 Solution: Strategy551552Problem: Need undo/redo or action queuing553 Solution: Command554555Problem: Object behavior changes based on state, lots of if/else556 Solution: State557558Problem: Multiple handlers may process a request559 Solution: Chain of Responsibility560561Problem: Same algorithm skeleton, different steps562 Solution: Template Method563564Problem: Tree structure, treat individual and composite uniformly565 Solution: Composite566```567568### When NOT to Use Patterns569```570Over-engineering warning signs:571 - You are applying a pattern before you have the problem it solves572 - Adding a pattern makes code harder to understand573 - You added 3 new classes to solve a 10-line problem574 - You cannot explain WHY the pattern helps here575576Simple code beats clever code:577 if (type === 'email') sendEmail(...);578 if (type === 'sms') sendSMS(...);579 // This is fine for 2 cases580 // Only extract to Factory when you have 5+ cases or need runtime switching581582YAGNI — You Ain't Gonna Need It583 Do not add patterns for future flexibility that may never materialize584 Wait until you feel the pain, then refactor585586Patterns that are often overused:587 Singleton: Usually a sign of global state problem — use DI instead588 Abstract Factory: Often unnecessary unless supporting multiple product families589 Visitor: Complex, hard to understand — consider simpler alternatives590```591592---593594## Best Practices595596- Learn patterns by the PROBLEM they solve, not by their structure597- Apply patterns reactively — wait until you feel the pain, then refactor598- Patterns are a shared vocabulary — use their names in code reviews and discussions599- Most patterns are workarounds for missing language features — know your language600- Functional languages have different patterns: monads, functors, currying, composition601- The best code is the simplest code that solves the problem602- Patterns should make code MORE readable, not less603604---605606## Common Pitfalls607608| Pitfall | Problem | Fix |609|---|---|---|610| Pattern hunting | Applying patterns before having the problem | Wait for pain, then refactor |611| Singleton overuse | Global mutable state, hard to test | Use dependency injection instead |612| Over-abstraction | 5 interfaces for a 2-case problem | Start simple, abstract when needed |613| Pattern for pattern sake | Complex code with no benefit | Always ask: does this make code clearer? |614| Ignoring language features | Java patterns in Python | Use language idioms first |615| Missing observer cleanup | Memory leaks from forgotten subscriptions | Always return and call unsubscribe |616| Deep decorator chains | Hard to debug, stack traces nightmare | Keep decorator chains shallow |617| Visitor complexity | Hard to add new element types | Consider simpler double-dispatch or match |618619---620621## Related Skills622623- **clean-architecture**: For architectural patterns624- **domain-driven-design**: For domain modeling patterns625- **typescript-expert**: For type-safe pattern implementation626- **code-review-expert**: For recognizing patterns in code reviews627- **refactoring-expert**: For refactoring toward patterns628- **functional-programming**: For FP alternatives to OOP patterns