Design Patterns Guide: The 10 Most Used Software Design Patterns
Design patterns are proven solutions in the software world. Cataloged by the Gang of Four (GoF) in 1994, these patterns remain fundamental to modern software development.
What Is a Design Pattern?
A design pattern is a reusable, proven solution to commonly occurring problems in software design. It's not ready-made code, but a solution template.
Three Categories
| Category | Purpose | Examples | |----------|---------|----------| | Creational | Object creation | Factory, Singleton, Builder | | Structural | Object composition | Adapter, Decorator, Facade | | Behavioral | Object behavior | Observer, Strategy, Command |
Creational Patterns
1. Factory Method
Delegates object creation logic to subclasses:
interface Notification {
send(message: string): void;
}
class EmailNotification implements Notification {
send(message: string) { /* send email */ }
}
class SMSNotification implements Notification {
send(message: string) { /* send SMS */ }
}
function createNotification(type: string): Notification {
switch(type) {
case 'email': return new EmailNotification();
case 'sms': return new SMSNotification();
default: throw new Error('Unknown type');
}
}
2. Singleton
Guarantees only one instance of a class exists:
class DatabaseConnection {
private static instance: DatabaseConnection;
private constructor() {}
static getInstance(): DatabaseConnection {
if (!DatabaseConnection.instance) {
DatabaseConnection.instance = new DatabaseConnection();
}
return DatabaseConnection.instance;
}
}
3. Builder
Constructs complex objects step by step:
const order = new OrderBuilder()
.setCustomer('Alice')
.addItem('Laptop', 1500)
.addItem('Mouse', 20)
.setShipping('express')
.build();
Structural Patterns
4. Adapter
Makes incompatible interfaces work together:
class OldPaymentSystem {
processPayment(amount: number) { /* ... */ }
}
interface ModernPayment {
pay(amount: number, currency: string): Promise<boolean>;
}
class PaymentAdapter implements ModernPayment {
constructor(private oldSystem: OldPaymentSystem) {}
async pay(amount: number, currency: string) {
this.oldSystem.processPayment(amount);
return true;
}
}
5. Decorator
Adds new behaviors to existing objects dynamically.
6. Facade
Hides complex subsystems behind a simple interface:
class OrderFacade {
async placeOrder(cart: Cart) {
await inventory.reserve(cart.items);
const payment = await paymentService.charge(cart.total);
await shippingService.schedule(cart.address);
await notificationService.sendConfirmation(cart.customer);
return { orderId: generateId(), payment, status: 'confirmed' };
}
}
Behavioral Patterns
7. Observer
Automatically notifies subscribed objects of changes in another object.
8. Strategy
Makes algorithms interchangeable (swappable):
interface SortStrategy {
sort(data: number[]): number[];
}
class Sorter {
constructor(private strategy: SortStrategy) {}
execute(data: number[]) { return this.strategy.sort(data); }
}
9. Command
Encapsulates requests as objects, enabling undo/redo functionality.
10. Template Method
Defines the skeleton of an algorithm, deferring details to subclasses.
Which Pattern When?
| Problem | Pattern | |---------|---------| | Creating different object types | Factory | | Single instance needed | Singleton | | Build complex objects step by step | Builder | | Connect incompatible interfaces | Adapter | | Add features dynamically | Decorator | | Hide complexity | Facade | | React to changes | Observer | | Swap algorithms | Strategy |
Conclusion
Design patterns are a tool, not a goal. Overusing them is just as harmful as not using them at all. Apply the right pattern to the right problem.
Practice design patterns interactively on the Foundation career path at LabLudus.