Introduction
Design patterns are reusable solutions to common problems in software design. They’re not finished code that can be copied directly, but templates that help you solve problems in your specific context. Originally documented in the seminal “Design Patterns” book (1994), these patterns have become essential knowledge for every software engineer.
This guide covers the most important patterns with practical examples you can apply today.
What Are Design Patterns?
Why Design Patterns Matter
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ WHY DESIGN PATTERNS MATTER โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ Benefits: โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ Reusable solutions to common problems โ โ
โ โ โ Shared vocabulary for team communication โ โ
โ โ โ Proven architectures that scale โ โ
โ โ โ Better code organization and maintainability โ โ
โ โ โ Easier onboarding for new team members โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ The Three Categories: โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ CREATIONAL: How objects are created โ โ
โ โ โข Singleton, Factory, Abstract Factory โ โ
โ โ โข Builder, Prototype โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค โ
โ โ STRUCTURAL: How objects are composed โ โ
โ โ โข Adapter, Bridge, Composite, Decorator โ โ
โ โ โข Facade, Flyweight, Proxy โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค โ
โ โ BEHAVIORAL: How objects communicate โ โ
โ โ โข Observer, Strategy, Command, Iterator โ โ
โ โ โข Mediator, Memento, State, Template Method โ โ
โ โ โข Chain of Responsibility, Visitor โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Creational Patterns
1. Singleton Pattern
Ensures a class has only one instance and provides a global access point.
"""Singleton Pattern - Python implementation."""
class Singleton:
"""Thread-safe Singleton implementation."""
_instance = None
_lock = None
def __new__(cls):
if cls._instance is None:
if cls._lock is None:
import threading
cls._lock = threading.Lock()
with cls._lock:
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self):
if not hasattr(self, '_initialized'):
self._initialized = True
class DatabaseConnection:
"""Singleton database connection."""
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance.connection = None
return cls._instance
def connect(self, url: str):
"""Establish database connection."""
if self.connection is None:
print(f"Connecting to {url}...")
self.connection = f"Connection to {url}"
return self.connection
def query(self, sql: str):
"""Execute query."""
return f"Results from: {sql}"
# Usage
db1 = DatabaseConnection()
db2 = DatabaseConnection()
print(f"Same instance: {db1 is db2}") # True
db1.connect("postgresql://localhost/mydb")
print(db2.query("SELECT * FROM users"))
// Singleton Pattern - JavaScript/TypeScript
class DatabaseConnection {
private static instance: DatabaseConnection | null = null;
private connection: string | null = null;
private constructor() {}
static getInstance(): DatabaseConnection {
if (!DatabaseConnection.instance) {
DatabaseConnection.instance = new DatabaseConnection();
}
return DatabaseConnection.instance;
}
connect(url: string): void {
if (!this.connection) {
console.log(`Connecting to ${url}...`);
this.connection = `Connection to ${url}`;
}
}
query(sql: string): string {
return `Results from: ${sql}`;
}
}
// Usage
const db1 = DatabaseConnection.getInstance();
const db2 = DatabaseConnection.getInstance();
console.log(db1 === db2); // true
2. Factory Pattern
Creates objects without specifying the exact class of object that will be created.
"""Factory Pattern - Python implementation."""
from abc import ABC, abstractmethod
from typing import Optional
# Product interface
class Notification(ABC):
@abstractmethod
def send(self, message: str) -> None:
pass
# Concrete Products
class EmailNotification(Notification):
def send(self, message: str) -> None:
print(f"๐ง Sending EMAIL: {message}")
class SMSNotification(Notification):
def send(self, message: str) -> None:
print(f"๐ฑ Sending SMS: {message}")
class PushNotification(Notification):
def send(self, message: str) -> None:
print(f"๐ Sending PUSH: {message}")
# Factory
class NotificationFactory:
@staticmethod
def create_notification(channel: str) -> Notification:
"""Create notification based on channel."""
channels = {
'email': EmailNotification,
'sms': SMSNotification,
'push': PushNotification
}
notification_class = channels.get(channel.lower())
if not notification_class:
raise ValueError(f"Unknown channel: {channel}")
return notification_class()
# Usage
factory = NotificationFactory()
notifications = ['email', 'sms', 'push']
for channel in notifications:
notification = factory.create_notification(channel)
notification.send(f"Hello via {channel}!")
3. Builder Pattern
Separates the construction of a complex object from its representation.
"""Builder Pattern - Python implementation."""
from dataclasses import dataclass, field
from typing import List, Optional
@dataclass
class Pizza:
"""Complex object being built."""
size: str = "medium"
crust: str = "regular"
toppings: List[str] = field(default_factory=list)
extra_cheese: bool = False
spicy: bool = False
class PizzaBuilder:
"""Builder for Pizza."""
def __init__(self):
self._pizza = Pizza()
def set_size(self, size: str) -> 'PizzaBuilder':
self._pizza.size = size
return self
def set_crust(self, crust: str) -> 'PizzaBuilder':
self._pizza.crust = crust
return self
def add_topping(self, topping: str) -> 'PizzaBuilder':
self._pizza.toppings.append(topping)
return self
def add_extra_cheese(self) -> 'PizzaBuilder':
self._pizza.extra_cheese = True
return self
def make_spicy(self) -> 'PizzaBuilder':
self._pizza.spicy = True
return self
def build(self) -> Pizza:
pizza = self._pizza
self._pizza = Pizza() # Reset for next build
return pizza
class PizzaDirector:
"""Director - optional helper for common configurations."""
def __init__(self, builder: PizzaBuilder):
self._builder = builder
def make_pepperoni(self) -> Pizza:
return (self._builder
.set_size("large")
.set_crust("thin")
.add_topping("pepperoni")
.add_topping("cheese")
.build())
def make_vegetarian(self) -> Pizza:
return (self._builder
.set_size("medium")
.set_crust("regular")
.add_topping("mushrooms")
.add_topping("peppers")
.add_topping("olives")
.add_extra_cheese()
.build())
# Usage
builder = PizzaBuilder()
# Method chaining
pizza1 = (builder
.set_size("large")
.set_crust("thick")
.add_topping("pepperoni")
.add_topping("mushrooms")
.add_extra_cheese()
.build())
print(f"๐ {pizza1.size}, {pizza1.crust} crust")
print(f" Toppings: {pizza1.toppings}")
print(f" Extra cheese: {pizza1.extra_cheese}")
# Using Director
director = PizzaDirector(builder)
pizza2 = director.make_pepperoni()
print(f"\n๐ {pizza2.size}, {pizza2.crust} crust")
print(f" Toppings: {pizza2.toppings}")
Structural Patterns
4. Adapter Pattern
Converts the interface of a class into another interface clients expect.
"""Adapter Pattern - Python implementation."""
from abc import ABC, abstractmethod
from typing import Any
# Target interface (what client expects)
class PaymentProcessor(ABC):
@abstractmethod
def process_payment(self, amount: float, currency: str) -> dict:
pass
# Adaptee (existing class with incompatible interface)
class StripeAPI:
"""Third-party Stripe API."""
def charge(self, amount_cents: int, currency: str) -> dict:
"""Stripe uses cents, not dollars."""
return {
'transaction_id': 'stripe_12345',
'amount': amount_cents,
'currency': currency.upper(),
'status': 'succeeded'
}
class PayPalAPI:
"""Third-party PayPal API."""
def make_payment(self, amount: float, currency_code: str,
payment_type: str = 'order') -> dict:
"""PayPal has different parameter names."""
return {
'order_id': 'paypal_67890',
'amount': amount,
'currency': currency_code,
'type': payment_type
}
# Adapters
class StripeAdapter(PaymentProcessor):
"""Adapter for Stripe API."""
def __init__(self, stripe_api: StripeAPI):
self._stripe = stripe_api
def process_payment(self, amount: float, currency: str) -> dict:
# Convert dollars to cents
amount_cents = int(amount * 100)
result = self._stripe.charge(amount_cents, currency)
return {
'success': result['status'] == 'succeeded',
'transaction_id': result['transaction_id'],
'amount': amount,
'currency': currency
}
class PayPalAdapter(PaymentProcessor):
"""Adapter for PayPal API."""
def __init__(self, paypal_api: PayPalAPI):
self._paypal = paypal_api
def process_payment(self, amount: float, currency: str) -> dict:
result = self._paypal.make_payment(amount, currency, 'instant')
return {
'success': True,
'transaction_id': result['order_id'],
'amount': amount,
'currency': currency
}
# Usage
def checkout(payment_processor: PaymentProcessor, amount: float):
"""Client code works with PaymentProcessor interface."""
result = payment_processor.process_payment(amount, 'USD')
print(f"Payment processed: {result}")
# Different adapters, same interface
stripe = StripeAdapter(StripeAPI())
paypal = PayPalAdapter(PayPalAPI())
checkout(stripe, 99.99) # Works!
checkout(paypal, 49.99) # Works!
5. Decorator Pattern
Attaches additional responsibilities to an object dynamically.
"""Decorator Pattern - Python implementation."""
from abc import ABC, abstractmethod
from functools import wraps
# Component interface
class Coffee(ABC):
@abstractmethod
def cost(self) -> float:
pass
@abstractmethod
def description(self) -> str:
pass
# Concrete Component
class SimpleCoffee(Coffee):
def cost(self) -> float:
return 2.00
def description(self) -> str:
return "Simple Coffee"
# Base Decorator
class CoffeeDecorator(Coffee):
def __init__(self, coffee: Coffee):
self._coffee = coffee
def cost(self) -> float:
return self._coffee.cost()
def description(self) -> str:
return self._coffee.description()
# Concrete Decorators
class MilkDecorator(CoffeeDecorator):
def cost(self) -> float:
return self._coffee.cost() + 0.50
def description(self) -> str:
return f"{self._coffee.description()}, Milk"
class SugarDecorator(CoffeeDecorator):
def cost(self) -> float:
return self._coffee.cost() + 0.25
def description(self) -> str:
return f"{self._coffee.description()}, Sugar"
class WhippedCreamDecorator(CoffeeDecorator):
def cost(self) -> float:
return self._coffee.cost() + 0.75
def description(self) -> str:
return f"{self._coffee.description()}, Whipped Cream"
class CaramelDecorator(CoffeeDecorator):
def cost(self) -> float:
return self._coffee.cost() + 0.60
def description(self) -> str:
return f"{self._coffee.description()}, Caramel"
# Usage
coffee = SimpleCoffee()
print(f"{coffee.description()} = ${coffee.cost():.2f}")
coffee = MilkDecorator(coffee)
print(f"{coffee.description()} = ${coffee.cost():.2f}")
coffee = SugarDecorator(coffee)
print(f"{coffee.description()} = ${coffee.cost():.2f}")
# Chaining decorators
coffee = CaramelDecorator(
WhippedCreamDecorator(
MilkDecorator(
SimpleCoffee()
)
)
)
print(f"\n{cffee.description()} = ${coffee.cost():.2f}")
# Wait, typo above - should be "coffee"
6. Facade Pattern
Provides a simplified interface to a complex subsystem.
"""Facade Pattern - Python implementation."""
from typing import List, Dict, Any
import time
# Complex subsystem classes
class VideoFile:
def __init__(self, filename: str):
self.filename = filename
self.codec = filename.split('.')[-1]
class CodecFactory:
@staticmethod
def extract(file: VideoFile) -> str:
# In real code, would extract actual codec
return f"{file.codec}_codec"
class BitrateReader:
def read(self, filename: str, codec: str) -> bytes:
# Read video data
return b"video_data"
class AudioMixer:
def fix(self, data: bytes) -> bytes:
# Fix audio
return data
class VideoEncoder:
def encode(self, data: bytes, codec: str, output: str):
# Encode video
print(f"Encoding {output} with {codec} codec...")
time.sleep(0.1)
# Facade
class VideoConverter:
"""
Facade that provides simple interface to complex subsystem.
"""
def convert(self, filename: str, format: str) -> str:
# All complexity hidden behind facade
file = VideoFile(filename)
codec = CodecFactory.extract(file)
data = BitrateReader().read(filename, codec)
data = AudioMixer().fix(data)
output = filename.replace(file.codec, format)
VideoEncoder().encode(data, codec, output)
return output
# Usage - Simple!
converter = VideoConverter()
result = converter.convert("funny_video.ogg", "mp4")
print(f"Converted to: {result}")
# Client doesn't need to know about:
# - VideoFile, CodecFactory, BitrateReader
# - AudioMixer, VideoEncoder
# - All the complex steps
Behavioral Patterns
7. Observer Pattern
Defines a one-to-many dependency between objects so when one changes state, all dependents are notified.
"""Observer Pattern - Python implementation."""
from abc import ABC, abstractmethod
from typing import List
import time
# Subject
class NewsAgency:
"""The subject being observed."""
def __init__(self):
self._subscribers: List[Observer] = []
self._latest_news: str = ""
def subscribe(self, observer: 'Observer'):
self._subscribers.append(observer)
def unsubscribe(self, observer: 'Observer'):
self._subscribers.remove(observer)
def notify(self):
for observer in self._subscribers:
observer.update(self._latest_news)
def publish_news(self, news: str):
print(f"\n๐ฐ Publishing: {news}")
self._latest_news = news
self.notify()
# Observer interface
class Observer(ABC):
@abstractmethod
def update(self, news: str):
pass
# Concrete Observers
class NewsChannel(Observer):
def __init__(self, name: str):
self.name = name
def update(self, news: str):
print(f"๐บ {self.name} broadcasting: {news}")
class NewsSubscriber(Observer):
def __init__(self, name: str):
self.name = name
def update(self, news: str):
print(f"๐ค {self.name} received: {news}")
class NewsAnalytics(Observer):
def __init__(self):
self.news_count = 0
def update(self, news: str):
self.news_count += 1
print(f"๐ Analytics: Received {self.news_count} news items")
# Usage
agency = NewsAgency()
# Different observers
cnn = NewsChannel("CNN")
bbc = NewsChannel("BBC")
alice = NewsSubscriber("Alice")
bob = NewsSubscriber("Bob")
analytics = NewsAnalytics()
# Subscribe
agency.subscribe(cnn)
agency.subscribe(bbc)
agency.subscribe(alice)
agency.subscribe(bob)
agency.subscribe(analytics)
# Publish news
agency.publish_news("Breaking: Python 4.0 announced!")
agency.publish_news("Tech: AI writes better code than humans")
# Unsubscribe
agency.unsubscribe(bbc)
agency.publish_news("Sports: Team wins championship")
8. Strategy Pattern
Defines a family of algorithms, encapsulates each one, and makes them interchangeable.
"""Strategy Pattern - Python implementation."""
from abc import ABC, abstractmethod
from typing import List
# Strategy interface
class SortStrategy(ABC):
@abstractmethod
def sort(self, data: List[int]) -> List[int]:
pass
# Concrete Strategies
class BubbleSort(SortStrategy):
def sort(self, data: List[int]) -> List[int]:
arr = data.copy()
n = len(arr)
for i in range(n):
for j in range(0, n-i-1):
if arr[j] > arr[j+1]:
arr[j], arr[j[j+1]] = arr[j+1], arr[j]
return arr
class QuickSort(SortStrategy):
def sort(self, data: List[int]) -> List[int]:
if len(data) <= 1:
return data
pivot = data[len(data) // 2]
left = [x for x in data if x < pivot]
middle = [x for x in data if x == pivot]
right = [x for x in data if x > pivot]
return self.sort(left) + middle + self.sort(right)
class MergeSort(SortStrategy):
def sort(self, data: List[int]) -> List[int]:
if len(data) <= 1:
return data
mid = len(data) // 2
left = self.sort(data[:mid])
right = self.sort(data[mid:])
return self._merge(left, right)
def _merge(self, left: List[int], right: List[int]) -> List[int]:
result = []
i = j = 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
result.extend(left[i:])
result.extend(right[j:])
return result
# Context
class Sorter:
def __init__(self, strategy: SortStrategy):
self._strategy = strategy
def set_strategy(self, strategy: SortStrategy):
self._strategy = strategy
def sort(self, data: List[int]) -> List[int]:
return self._strategy.sort(data)
# Usage
data = [64, 34, 25, 12, 22, 11, 90]
sorter = Sorter(BubbleSort())
print(f"Bubble sort: {sorter.sort(data)}")
sorter.set_strategy(QuickSort())
print(f"Quick sort: {sorter.sort(data)}")
sorter.set_strategy(MergeSort())
print(f"Merge sort: {sorter.sort(data)}")
# Dynamic selection based on data size
def get_optimal_strategy(data_size: int) -> SortStrategy:
if data_size < 100:
return BubbleSort() # Simple, no overhead
elif data_size < 10000:
return QuickSort() # Good average case
else:
return MergeSort() # Guaranteed O(n log n)
sorter.set_strategy(get_optimal_strategy(len(data)))
9. Command Pattern
Encapsulates a request as an object, allowing parameterization and queuing of requests.
"""Command Pattern - Python implementation."""
from abc import ABC, abstractmethod
from typing import List
from dataclasses import dataclass
from datetime import datetime
# Command interface
class Command(ABC):
@abstractmethod
def execute(self) -> None:
pass
@abstractmethod
def undo(self) -> None:
pass
# Receiver
class TextEditor:
"""The object that performs the actual work."""
def __init__(self):
self.content: str = ""
def insert(self, text: str, position:.content = self.content int):
self[:position] + text + self.content[position:]
print(f"Inserted '{text}' at position {position}")
def delete(self, start: int, length: int):
deleted = self.content[start:start+length]
self.content = self.content[:start] + self.content[start+length:]
print(f"Deleted '{deleted}' from position {start}")
def get_content(self) -> str:
return self.content
# Concrete Commands
class InsertCommand(Command):
def __init__(self, editor: TextEditor, text: str, position: int):
self.editor = editor
self.text = text
self.position = position
def execute(self):
self.editor.insert(self.text, self.position)
def undo(self):
self.editor.delete(self.position, len(self.text))
class DeleteCommand(Command):
def __init__(self, editor: TextEditor, start: int, length: int):
self.editor = editor
self.start = start
self.length = length
self.deleted_text = ""
def execute(self):
self.deleted_text = self.editor.content[self.start:self.start+self.length]
self.editor.delete(self.start, self.length)
def undo(self):
self.editor.insert(self.deleted_text, self.start)
# Invoker
class CommandManager:
"""Manages command execution and undo/redo."""
def __init__(self):
self._history: List[Command] = []
self._redo_stack: List[Command] = []
def execute_command(self, command: Command):
command.execute()
self._history.append(command)
self._redo_stack.clear() # Clear redo on new command
def undo(self):
if not self.history:
print("Nothing to undo")
return
command = self._history.pop()
command.undo()
self._redo_stack.append(command)
def redo(self):
if not self._redo_stack:
print("Nothing to redo")
return
command = self._redo_stack.pop()
command.execute()
self._history.append(command)
@property
def history(self) -> List[Command]:
return self._history
# Usage
editor = TextEditor()
manager = CommandManager()
# Type something
manager.execute_command(InsertCommand(editor, "Hello", 0))
print(f"Content: '{editor.get_content()}'")
manager.execute_command(InsertCommand(editor, " World", 5))
print(f"Content: '{editor.get_content()}'")
# Undo
manager.undo()
print(f"After undo: '{editor.get_content()}'")
# Redo
manager.redo()
print(f"After redo: '{editor.get_content()}'")
# Delete
manager.execute_command(DeleteCommand(editor, 5, 6))
print(f"After delete: '{editor.get_content()}'")
Modern JavaScript/TypeScript Patterns
Module Pattern
// Module Pattern - JavaScript
// Using IIFE for encapsulation
const UserModule = (function() {
// Private variables
const _users = new Map();
let _idCounter = 1;
// Private function
function _generateId() {
return _idCounter++;
}
// Public API
return {
add(name, email) {
const id = _generateId();
_users.set(id, { id, name, email });
return id;
},
get(id) {
return _users.get(id);
},
getAll() {
return Array.from(_users.values());
},
remove(id) {
return _users.delete(id);
},
count() {
return _users.size;
}
};
})();
// Usage
UserModule.add('Alice', '[email protected]');
UserModule.add('Bob', '[email protected]');
console.log(UserModule.getAll());
console.log(UserModule.count());
Mixin Pattern
// Mixin Pattern - TypeScript
// Base class
class Person {
constructor(public name: string) {}
greet() {
console.log(`Hello, I'm ${this.name}`);
}
}
// Mixin
function Timestamped<T extends new (...args: any[]) => any>(Constructor: T) {
return class extends Constructor {
createdAt = new Date();
updatedAt = new Date();
touch() {
this.updatedAt = new Date();
}
};
}
function Serializable<T extends new (...args: any[]) => any>(Constructor: T) {
return class extends Constructor {
toJSON() {
return Object.getOwnPropertyNames(this).reduce((acc, key) => {
acc[key] = (this as any)[key];
return acc;
}, {} as Record<string, any>);
}
};
}
// Apply mixins
class User extends Timestamped(Serializable(Person)) {
constructor(name: string, public email: string) {
super(name);
}
}
// Usage
const user = new User('Alice', '[email protected]');
user.greet();
user.touch();
console.log(user.toJSON());
Pattern Selection Guide
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ WHEN TO USE WHICH PATTERN โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ Need ONE instance? โ
โ โโโบ Singleton โ
โ โ
โ Creating complex objects? โ
โ โโโบ Builder (method chaining) โ
โ โ
โ Creating different types of objects? โ
โ โโโบ Factory (Abstract Factory) โ
โ โ
โ Need to wrap incompatible interface? โ
โ โโโบ Adapter โ
โ โ
โ Need to add features dynamically? โ
โ โโโบ Decorator โ
โ โ
โ Need simplified interface to complex system? โ
โ โโโบ Facade โ
โ โ
โ Need to notify multiple objects of changes? โ
โ โโโบ Observer (Pub/Sub) โ
โ โ
โ Need to swap algorithms at runtime? โ
โ โโโบ Strategy โ
โ โ
โ Need to track operations for undo/redo? โ
โ โโโบ Command โ
โ โ
โ Need to represent part-whole hierarchies? โ
โ โโโบ Composite โ
โ โ
โ Need controlled access to resource? โ
โ โโโบ Proxy โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Anti-Patterns to Avoid
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ COMMON ANTI-PATTERNS โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ God Object โ โ
โ โ โข Single class knows/does too much โ โ
โ โ โข Solution: Split into focused classes โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Spaghetti Code โ โ
โ โ โข No clear structure, tangled control flow โ โ
โ โ โข Solution: Refactor to patterns, use functions โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Golden Hammer โ โ
โ โ โข Trying to fit every problem to one solution โ โ
โ โ โข Solution: Choose right tool for the job โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Shotgun Surgery โ โ
โ โ โข One change requires modifications across many files โ โ
โ โ โข Solution: Consolidate related changes โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Deep Inheritance โ โ
โ โ โข Deep class hierarchy is rigid and fragile โ โ
โ โ โข Solution: Prefer composition over inheritance โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Conclusion
Design patterns are powerful tools in a software engineer’s toolkit. Key takeaways:
- Patterns are starting points, not rigid rules - Adapt them to your context
- Prefer composition over inheritance - More flexible and testable
- Know the problem before choosing a pattern - Don’t force patterns where not needed
- Start simple - Add complexity only when needed
- Patterns are about communication - Using named patterns helps teams communicate
Remember: The best code is readable, maintainable, and solves the problem at hand. Use patterns when they help, not just because they exist.
External Resources
Books
- “Design Patterns: Elements of Reusable Object-Oriented Software” - Gang of Four
- “Head First Design Patterns” by Freeman & Robson
- “Refactoring” by Martin Fowler
Online Resources
Related Articles on This Site
- Technical Debt Management Strategies
- Code Review Best Practices
- Refactoring Strategies and Techniques
Comments