Skip to main content
โšก Calmops

Design Patterns for Modern Applications: A Comprehensive Guide

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:

  1. Patterns are starting points, not rigid rules - Adapt them to your context
  2. Prefer composition over inheritance - More flexible and testable
  3. Know the problem before choosing a pattern - Don’t force patterns where not needed
  4. Start simple - Add complexity only when needed
  5. 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

Online Resources

Comments