Software Design Patterns
| Category: Programming | Updated: 2026-02-15 |
Design patterns are reusable solutions to common problems in software design. They represent best practices refined over time by experienced developers. This reference covers the classic Gang of Four (GoF) patterns organized by type: Creational, Structural, and Behavioral.
Quick Reference Table
| Pattern | Type | Purpose | Use When |
|---|---|---|---|
| Singleton | Creational | One instance only | Global access to single object |
| Factory Method | Creational | Defer instantiation to subclasses | Unknown object types at compile time |
| Abstract Factory | Creational | Families of related objects | Need consistent interface families |
| Builder | Creational | Construct complex objects step-by-step | Many constructor parameters |
| Prototype | Creational | Clone existing objects | Expensive object creation |
| Adapter | Structural | Make incompatible interfaces work | Need to use existing class with incompatible interface |
| Bridge | Structural | Separate abstraction from implementation | Avoid permanent binding |
| Composite | Structural | Tree structure of objects | Part-whole hierarchies |
| Decorator | Structural | Add behavior dynamically | Need to add responsibilities to individual objects |
| Facade | Structural | Simplified interface to complex system | Simplify complex subsystem |
| Proxy | Structural | Surrogate for another object | Control access to object |
| Observer | Behavioral | Notify dependents of state changes | One-to-many dependencies |
| Strategy | Behavioral | Encapsulate algorithms | Multiple interchangeable algorithms |
| Command | Behavioral | Encapsulate requests as objects | Parameterize objects with operations |
| Iterator | Behavioral | Sequential access to elements | Traverse collection without exposing representation |
| State | Behavioral | Change behavior based on state | Behavior depends on state |
| Template Method | Behavioral | Define algorithm skeleton | Common algorithm with varying steps |
| Chain of Responsibility | Behavioral | Pass request along chain | Multiple objects might handle request |
Creational Patterns
Patterns for object creation mechanisms, increasing flexibility and reuse.
Singleton
Intent: Ensure a class has only one instance and provide global access to it.
Use Cases
- Database connections
- Configuration managers
- Logging services
- Thread pools
- Caches
Implementation (Python)
class Singleton:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
class DatabaseConnection(Singleton):
def __init__(self):
if not hasattr(self, 'initialized'):
self.connection = "Database Connection"
self.initialized = True
def query(self, sql):
return f"Executing: {sql}"
# Usage
db1 = DatabaseConnection()
db2 = DatabaseConnection()
print(db1 is db2) # True - same instance
Implementation (Java)
public class Singleton {
private static Singleton instance;
private Singleton() {
// Private constructor
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
// Thread-safe version
public class ThreadSafeSingleton {
private static volatile ThreadSafeSingleton instance;
private ThreadSafeSingleton() {}
public static ThreadSafeSingleton getInstance() {
if (instance == null) {
synchronized (ThreadSafeSingleton.class) {
if (instance == null) {
instance = new ThreadSafeSingleton();
}
}
}
return instance;
}
}
Pros & Cons
✅ Controlled access to sole instance
✅ Reduced namespace pollution
✅ Permits refinement through subclassing
❌ Difficult to test (global state)
❌ Violates Single Responsibility Principle
❌ Can hide bad design
Factory Method
Intent: Define an interface for creating objects, but let subclasses decide which class to instantiate.
Use Cases
- Framework libraries
- When class can’t anticipate object types to create
- Delegating responsibility to helper subclasses
Implementation (Python)
from abc import ABC, abstractmethod
class Animal(ABC):
@abstractmethod
def speak(self):
pass
class Dog(Animal):
def speak(self):
return "Woof!"
class Cat(Animal):
def speak(self):
return "Meow!"
class AnimalFactory:
def create_animal(self, animal_type):
if animal_type == "dog":
return Dog()
elif animal_type == "cat":
return Cat()
else:
raise ValueError(f"Unknown animal type: {animal_type}")
# Usage
factory = AnimalFactory()
dog = factory.create_animal("dog")
cat = factory.create_animal("cat")
print(dog.speak()) # Woof!
print(cat.speak()) # Meow!
Implementation (Java)
interface Animal {
String speak();
}
class Dog implements Animal {
public String speak() {
return "Woof!";
}
}
class Cat implements Animal {
public String speak() {
return "Meow!";
}
}
abstract class AnimalFactory {
abstract Animal createAnimal();
public void makeSound() {
Animal animal = createAnimal();
System.out.println(animal.speak());
}
}
class DogFactory extends AnimalFactory {
Animal createAnimal() {
return new Dog();
}
}
class CatFactory extends AnimalFactory {
Animal createAnimal() {
return new Cat();
}
}
Abstract Factory
Intent: Provide an interface for creating families of related or dependent objects without specifying concrete classes.
Use Cases
- UI toolkits (Windows, Mac, Linux)
- Database drivers (MySQL, PostgreSQL)
- Cross-platform applications
Implementation (Python)
from abc import ABC, abstractmethod
# Abstract products
class Button(ABC):
@abstractmethod
def render(self):
pass
class Checkbox(ABC):
@abstractmethod
def render(self):
pass
# Concrete products - Windows
class WindowsButton(Button):
def render(self):
return "Rendering Windows button"
class WindowsCheckbox(Checkbox):
def render(self):
return "Rendering Windows checkbox"
# Concrete products - Mac
class MacButton(Button):
def render(self):
return "Rendering Mac button"
class MacCheckbox(Checkbox):
def render(self):
return "Rendering Mac checkbox"
# Abstract factory
class GUIFactory(ABC):
@abstractmethod
def create_button(self):
pass
@abstractmethod
def create_checkbox(self):
pass
# Concrete factories
class WindowsFactory(GUIFactory):
def create_button(self):
return WindowsButton()
def create_checkbox(self):
return WindowsCheckbox()
class MacFactory(GUIFactory):
def create_button(self):
return MacButton()
def create_checkbox(self):
return MacCheckbox()
# Usage
def create_ui(factory: GUIFactory):
button = factory.create_button()
checkbox = factory.create_checkbox()
print(button.render())
print(checkbox.render())
# Client code
import sys
if sys.platform == "win32":
factory = WindowsFactory()
else:
factory = MacFactory()
create_ui(factory)
Builder
Intent: Separate construction of complex object from its representation, allowing same construction process to create different representations.
Use Cases
- Complex object construction with many optional parameters
- Immutable objects
- Step-by-step construction
Implementation (Python)
class Pizza:
def __init__(self):
self.size = None
self.cheese = False
self.pepperoni = False
self.mushrooms = False
self.olives = False
def __str__(self):
toppings = []
if self.cheese: toppings.append("cheese")
if self.pepperoni: toppings.append("pepperoni")
if self.mushrooms: toppings.append("mushrooms")
if self.olives: toppings.append("olives")
return f"{self.size} pizza with {', '.join(toppings)}"
class PizzaBuilder:
def __init__(self):
self.pizza = Pizza()
def set_size(self, size):
self.pizza.size = size
return self
def add_cheese(self):
self.pizza.cheese = True
return self
def add_pepperoni(self):
self.pizza.pepperoni = True
return self
def add_mushrooms(self):
self.pizza.mushrooms = True
return self
def add_olives(self):
self.pizza.olives = True
return self
def build(self):
return self.pizza
# Usage
pizza = (PizzaBuilder()
.set_size("large")
.add_cheese()
.add_pepperoni()
.add_mushrooms()
.build())
print(pizza) # large pizza with cheese, pepperoni, mushrooms
Implementation (Java)
public class User {
private final String firstName;
private final String lastName;
private final int age;
private final String phone;
private final String address;
private User(UserBuilder builder) {
this.firstName = builder.firstName;
this.lastName = builder.lastName;
this.age = builder.age;
this.phone = builder.phone;
this.address = builder.address;
}
public static class UserBuilder {
private final String firstName; // Required
private final String lastName; // Required
private int age = 0; // Optional
private String phone = ""; // Optional
private String address = ""; // Optional
public UserBuilder(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public UserBuilder age(int age) {
this.age = age;
return this;
}
public UserBuilder phone(String phone) {
this.phone = phone;
return this;
}
public UserBuilder address(String address) {
this.address = address;
return this;
}
public User build() {
return new User(this);
}
}
}
// Usage
User user = new User.UserBuilder("John", "Doe")
.age(30)
.phone("555-1234")
.build();
Prototype
Intent: Specify kinds of objects to create using prototypical instance, and create new objects by copying this prototype.
Use Cases
- Object creation is expensive
- Avoid subclasses of object creator
- Keep number of classes minimal
Implementation (Python)
import copy
class Prototype:
def clone(self):
return copy.deepcopy(self)
class Shape(Prototype):
def __init__(self, x, y, color):
self.x = x
self.y = y
self.color = color
def __str__(self):
return f"{self.__class__.__name__} at ({self.x}, {self.y}) - {self.color}"
class Circle(Shape):
def __init__(self, x, y, color, radius):
super().__init__(x, y, color)
self.radius = radius
def __str__(self):
return f"Circle at ({self.x}, {self.y}) - {self.color}, radius: {self.radius}"
# Usage
original = Circle(0, 0, "red", 5)
clone = original.clone()
clone.x = 10
clone.color = "blue"
print(original) # Circle at (0, 0) - red, radius: 5
print(clone) # Circle at (10, 0) - blue, radius: 5
Structural Patterns
Patterns for assembling objects and classes into larger structures.
Adapter
Intent: Convert interface of a class into another interface clients expect. Allows classes with incompatible interfaces to work together.
Use Cases
- Legacy code integration
- Third-party library adaptation
- Interface standardization
Implementation (Python)
# Existing interface (Adaptee)
class EuropeanSocket:
def voltage(self):
return 230
def live(self):
return 1
def neutral(self):
return -1
# Target interface
class USASocket:
def voltage(self):
return 120
def live(self):
return 1
def neutral(self):
return -1
# Adapter
class SocketAdapter(USASocket):
def __init__(self, socket):
self.socket = socket
def voltage(self):
return 120 # Convert 230V to 120V
def live(self):
return self.socket.live()
def neutral(self):
return self.socket.neutral()
# Usage
euro_socket = EuropeanSocket()
adapter = SocketAdapter(euro_socket)
print(f"Voltage: {adapter.voltage()}V") # 120V
Decorator
Intent: Attach additional responsibilities to object dynamically. Provides flexible alternative to subclassing for extending functionality.
Use Cases
- Add responsibilities to individual objects
- Withdraw responsibilities
- Avoid explosion of subclasses
Implementation (Python)
from abc import ABC, abstractmethod
class Coffee(ABC):
@abstractmethod
def cost(self):
pass
@abstractmethod
def description(self):
pass
class SimpleCoffee(Coffee):
def cost(self):
return 2.0
def description(self):
return "Simple coffee"
class CoffeeDecorator(Coffee):
def __init__(self, coffee):
self._coffee = coffee
def cost(self):
return self._coffee.cost()
def description(self):
return self._coffee.description()
class MilkDecorator(CoffeeDecorator):
def cost(self):
return self._coffee.cost() + 0.5
def description(self):
return self._coffee.description() + ", milk"
class SugarDecorator(CoffeeDecorator):
def cost(self):
return self._coffee.cost() + 0.2
def description(self):
return self._coffee.description() + ", sugar"
# Usage
coffee = SimpleCoffee()
print(f"{coffee.description()}: ${coffee.cost()}")
coffee_with_milk = MilkDecorator(coffee)
print(f"{coffee_with_milk.description()}: ${coffee_with_milk.cost()}")
coffee_deluxe = SugarDecorator(MilkDecorator(SimpleCoffee()))
print(f"{coffee_deluxe.description()}: ${coffee_deluxe.cost()}")
Facade
Intent: Provide unified interface to set of interfaces in subsystem. Defines higher-level interface that makes subsystem easier to use.
Use Cases
- Simplify complex subsystems
- Layer system design
- Decouple subsystem from clients
Implementation (Python)
# Complex subsystem classes
class CPU:
def freeze(self):
print("CPU: Freezing")
def jump(self, position):
print(f"CPU: Jumping to {position}")
def execute(self):
print("CPU: Executing")
class Memory:
def load(self, position, data):
print(f"Memory: Loading data at {position}")
class HardDrive:
def read(self, sector, size):
return f"Data from sector {sector}"
# Facade
class ComputerFacade:
def __init__(self):
self.cpu = CPU()
self.memory = Memory()
self.hard_drive = HardDrive()
def start(self):
print("Computer starting...")
self.cpu.freeze()
self.memory.load(0, self.hard_drive.read(0, 1024))
self.cpu.jump(0)
self.cpu.execute()
print("Computer started!")
# Usage - simple interface
computer = ComputerFacade()
computer.start()
Proxy
Intent: Provide surrogate or placeholder for another object to control access to it.
Types
- Virtual Proxy: Lazy initialization
- Protection Proxy: Access control
- Remote Proxy: Remote object representation
- Smart Proxy: Additional actions when accessing object
Implementation (Python)
from abc import ABC, abstractmethod
class Image(ABC):
@abstractmethod
def display(self):
pass
class RealImage(Image):
def __init__(self, filename):
self.filename = filename
self._load_from_disk()
def _load_from_disk(self):
print(f"Loading image: {self.filename}")
def display(self):
print(f"Displaying: {self.filename}")
class ImageProxy(Image):
def __init__(self, filename):
self.filename = filename
self._real_image = None
def display(self):
if self._real_image is None:
self._real_image = RealImage(self.filename)
self._real_image.display()
# Usage
image = ImageProxy("large_photo.jpg")
# Image not loaded yet
print("Image proxy created")
# Image loaded on first access
image.display()
# Image already loaded, no reload
image.display()
Behavioral Patterns
Patterns for algorithms and assignment of responsibilities between objects.
Observer
Intent: Define one-to-many dependency so when one object changes state, all dependents are notified and updated automatically.
Use Cases
- Event handling systems
- MVC frameworks
- Pub/sub systems
Implementation (Python)
from abc import ABC, abstractmethod
class Observer(ABC):
@abstractmethod
def update(self, subject):
pass
class Subject:
def __init__(self):
self._observers = []
self._state = None
def attach(self, observer):
self._observers.append(observer)
def detach(self, observer):
self._observers.remove(observer)
def notify(self):
for observer in self._observers:
observer.update(self)
@property
def state(self):
return self._state
@state.setter
def state(self, value):
self._state = value
self.notify()
class ConcreteObserver(Observer):
def __init__(self, name):
self.name = name
def update(self, subject):
print(f"{self.name} notified. New state: {subject.state}")
# Usage
subject = Subject()
observer1 = ConcreteObserver("Observer 1")
observer2 = ConcreteObserver("Observer 2")
subject.attach(observer1)
subject.attach(observer2)
subject.state = "State A"
# Output:
# Observer 1 notified. New state: State A
# Observer 2 notified. New state: State A
Strategy
Intent: Define family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets algorithm vary independently from clients.
Use Cases
- Multiple algorithms for same task
- Conditional statements with many branches
- Different behavior variants
Implementation (Python)
from abc import ABC, abstractmethod
class PaymentStrategy(ABC):
@abstractmethod
def pay(self, amount):
pass
class CreditCardPayment(PaymentStrategy):
def __init__(self, card_number):
self.card_number = card_number
def pay(self, amount):
print(f"Paid ${amount} with credit card {self.card_number}")
class PayPalPayment(PaymentStrategy):
def __init__(self, email):
self.email = email
def pay(self, amount):
print(f"Paid ${amount} with PayPal account {self.email}")
class BitcoinPayment(PaymentStrategy):
def __init__(self, wallet_address):
self.wallet_address = wallet_address
def pay(self, amount):
print(f"Paid ${amount} with Bitcoin wallet {self.wallet_address}")
class ShoppingCart:
def __init__(self):
self.items = []
self.payment_strategy = None
def add_item(self, item, price):
self.items.append((item, price))
def set_payment_strategy(self, strategy):
self.payment_strategy = strategy
def checkout(self):
total = sum(price for _, price in self.items)
self.payment_strategy.pay(total)
# Usage
cart = ShoppingCart()
cart.add_item("Laptop", 1000)
cart.add_item("Mouse", 25)
cart.set_payment_strategy(CreditCardPayment("1234-5678-9012-3456"))
cart.checkout() # Paid $1025 with credit card
cart.set_payment_strategy(PayPalPayment("user@email.com"))
cart.checkout() # Paid $1025 with PayPal
Command
Intent: Encapsulate request as object, allowing parameterization of clients with different requests, queuing, logging, and undoable operations.
Use Cases
- Undo/redo functionality
- Transaction systems
- Macro recording
- Job queues
Implementation (Python)
from abc import ABC, abstractmethod
class Command(ABC):
@abstractmethod
def execute(self):
pass
@abstractmethod
def undo(self):
pass
class Light:
def on(self):
print("Light is ON")
def off(self):
print("Light is OFF")
class LightOnCommand(Command):
def __init__(self, light):
self.light = light
def execute(self):
self.light.on()
def undo(self):
self.light.off()
class LightOffCommand(Command):
def __init__(self, light):
self.light = light
def execute(self):
self.light.off()
def undo(self):
self.light.on()
class RemoteControl:
def __init__(self):
self.history = []
def execute_command(self, command):
command.execute()
self.history.append(command)
def undo_last(self):
if self.history:
command = self.history.pop()
command.undo()
# Usage
light = Light()
light_on = LightOnCommand(light)
light_off = LightOffCommand(light)
remote = RemoteControl()
remote.execute_command(light_on) # Light is ON
remote.execute_command(light_off) # Light is OFF
remote.undo_last() # Light is ON (undo off)
Iterator
Intent: Provide way to access elements of aggregate object sequentially without exposing underlying representation.
Implementation (Python)
class Book:
def __init__(self, title, author):
self.title = title
self.author = author
class BookCollection:
def __init__(self):
self.books = []
def add_book(self, book):
self.books.append(book)
def __iter__(self):
return BookIterator(self.books)
class BookIterator:
def __init__(self, books):
self._books = books
self._index = 0
def __iter__(self):
return self
def __next__(self):
if self._index < len(self._books):
book = self._books[self._index]
self._index += 1
return book
raise StopIteration
# Usage
collection = BookCollection()
collection.add_book(Book("1984", "George Orwell"))
collection.add_book(Book("Brave New World", "Aldous Huxley"))
for book in collection:
print(f"{book.title} by {book.author}")
State
Intent: Allow object to alter behavior when internal state changes. Object will appear to change its class.
Implementation (Python)
from abc import ABC, abstractmethod
class State(ABC):
@abstractmethod
def handle(self, context):
pass
class LockedState(State):
def handle(self, context):
print("Phone is locked. Unlocking...")
context.state = UnlockedState()
class UnlockedState(State):
def handle(self, context):
print("Phone is unlocked. Locking...")
context.state = LockedState()
class Phone:
def __init__(self):
self.state = LockedState()
def press_button(self):
self.state.handle(self)
# Usage
phone = Phone()
phone.press_button() # Phone is locked. Unlocking...
phone.press_button() # Phone is unlocked. Locking...
phone.press_button() # Phone is locked. Unlocking...
Template Method
Intent: Define skeleton of algorithm in operation, deferring some steps to subclasses. Lets subclasses redefine certain steps without changing algorithm structure.
Implementation (Python)
from abc import ABC, abstractmethod
class DataMiner(ABC):
def mine(self, path):
"""Template method"""
data = self.open_file(path)
raw_data = self.extract_data(data)
analysis = self.analyze(raw_data)
self.send_report(analysis)
self.close_file(data)
@abstractmethod
def open_file(self, path):
pass
@abstractmethod
def extract_data(self, file):
pass
def analyze(self, data):
"""Default implementation"""
return f"Analyzing: {data}"
def send_report(self, analysis):
"""Default implementation"""
print(f"Report: {analysis}")
@abstractmethod
def close_file(self, file):
pass
class CSVDataMiner(DataMiner):
def open_file(self, path):
print(f"Opening CSV file: {path}")
return f"CSV_FILE:{path}"
def extract_data(self, file):
print("Extracting CSV data")
return "CSV_DATA"
def close_file(self, file):
print("Closing CSV file")
class JSONDataMiner(DataMiner):
def open_file(self, path):
print(f"Opening JSON file: {path}")
return f"JSON_FILE:{path}"
def extract_data(self, file):
print("Extracting JSON data")
return "JSON_DATA"
def close_file(self, file):
print("Closing JSON file")
# Usage
csv_miner = CSVDataMiner()
csv_miner.mine("data.csv")
Chain of Responsibility
Intent: Avoid coupling sender of request to receiver by giving more than one object chance to handle request. Chain receiving objects and pass request along chain until object handles it.
Implementation (Python)
from abc import ABC, abstractmethod
class Handler(ABC):
def __init__(self):
self._next_handler = None
def set_next(self, handler):
self._next_handler = handler
return handler
@abstractmethod
def handle(self, request):
if self._next_handler:
return self._next_handler.handle(request)
return None
class AuthenticationHandler(Handler):
def handle(self, request):
if not request.get("authenticated"):
print("Authentication failed")
return False
print("Authentication successful")
return super().handle(request)
class AuthorizationHandler(Handler):
def handle(self, request):
if not request.get("authorized"):
print("Authorization failed")
return False
print("Authorization successful")
return super().handle(request)
class ValidationHandler(Handler):
def handle(self, request):
if not request.get("valid"):
print("Validation failed")
return False
print("Validation successful")
return super().handle(request)
# Usage
auth = AuthenticationHandler()
authz = AuthorizationHandler()
valid = ValidationHandler()
auth.set_next(authz).set_next(valid)
request1 = {"authenticated": True, "authorized": True, "valid": True}
auth.handle(request1)
request2 = {"authenticated": True, "authorized": False, "valid": True}
auth.handle(request2) # Stops at authorization
Pattern Relationships
Patterns That Work Together
- Adapter + Facade: Adapter adapts interface, Facade simplifies
- Strategy + Template Method: Strategy uses composition, Template Method uses inheritance
- Composite + Iterator: Iterator traverses Composite structure
- Observer + Mediator: Both decouple objects, Mediator centralizes communication
- Decorator + Composite: Both use recursive composition
- Command + Memento: Command stores state for undo using Memento
Pattern Selection Guide
Creating Objects:
- Many configurations → Builder
- Runtime type → Factory Method
- Families of objects → Abstract Factory
- Clone expensive objects → Prototype
- Single instance → Singleton
Connecting Objects:
- Incompatible interfaces → Adapter
- Simplified interface → Facade
- Control access → Proxy
- Add behavior → Decorator
- Tree structures → Composite
Behavior:
- Algorithm selection → Strategy
- State-dependent behavior → State
- Notify many → Observer
- Encapsulate request → Command
- Algorithm steps → Template Method
- Chain handlers → Chain of Responsibility
Anti-Patterns to Avoid
- God Object: Object knows/does too much
- Spaghetti Code: Unstructured, tangled code
- Golden Hammer: Using same solution for everything
- Premature Optimization: Optimizing before needed
- Copy-Paste Programming: Duplicating code instead of abstracting
- Not Invented Here: Refusing external solutions
- Analysis Paralysis: Over-analyzing, never implementing
SOLID Principles
Design patterns support SOLID principles:
S - Single Responsibility Principle
O - Open/Closed Principle
L - Liskov Substitution Principle
I - Interface Segregation Principle
D - Dependency Inversion Principle
Most patterns help achieve one or more SOLID principles.
When to Use Patterns
✅ Use patterns when:
- Problem clearly matches pattern
- Benefits outweigh complexity
- Team understands pattern
- Need proven solution
❌ Avoid patterns when:
- Simpler solution exists
- Over-engineering
- Pattern doesn’t fit problem
- Just learned pattern (resist hammer syndrome)
Remember: Patterns are tools, not rules. Don’t force them.