Introduction
Design patterns are reusable solutions to common problems in software design. In this comprehensive guide, we'll explore 5 essential design patterns with visual representations, simple explanations, and Python implementations.
π Table of Contents
Design Patterns
βββ 1. Singleton Pattern (Creational)
βββ 2. Factory Pattern (Creational)
βββ 3. Observer Pattern (Behavioral)
βββ 4. Strategy Pattern (Behavioral)
βββ 5. Decorator Pattern (Structural)
1. π Singleton Pattern
Mind Map
Singleton Pattern
|
+-----------------+-----------------+
| | |
Purpose Implementation Use Cases
| | |
One Instance Private Constructor Database
| Check if exists Connection
Global Access Return same obj Logger
Config Manager
What is it?
Ensures a class has only ONE instance and provides a global point of access to it.
Flow Diagram
βββββββββββββββββββ
β Client Request β
ββββββββββ¬βββββββββ
β
βΌ
ββββββββββββββββββββ
β Instance exists? β
ββββββ¬βββββββββ¬βββββ
β β
YES NO
β β
β βΌ
β βββββββββββββββ
β βCreate New β
β βInstance β
β ββββββββ¬βββββββ
β β
ββββββββββββ€
βΌ
ββββββββββββββββββββ
βReturn Instance β
ββββββββββββββββββββ
Python Implementation
class DatabaseConnection:
_instance = None
def __new__(cls):
if cls._instance is None:
print("Creating new database connection...")
cls._instance = super().__new__(cls)
cls._instance.connection = "Connected to DB"
return cls._instance
def query(self, sql):
return f"Executing: {sql}"
# Usage Example
if __name__ == "__main__":
# First call - creates instance
db1 = DatabaseConnection()
print(f"DB1: {db1.connection}")
# Second call - returns same instance
db2 = DatabaseConnection()
print(f"DB2: {db2.connection}")
# Verify they are the same
print(f"Same instance? {db1 is db2}") # Output: True
Output:
Creating new database connection...
DB1: Connected to DB
DB2: Connected to DB
Same instance? True
2. π Factory Pattern
Mind Map
Factory Pattern
|
+-----------------+-----------------+
| | |
Purpose Components Use Cases
| | |
Object Creation Factory Class Different DB
| Product Classes Connections
Decoupling Common Interface Payment Methods
Logic UI Components
What is it?
Creates objects without specifying the exact class to create. The factory decides which class to instantiate.
Flow Diagram
ββββββββββββββββ
β Client β
ββββββββ¬ββββββββ
β requests("type")
βΌ
ββββββββββββββββββββ
β Factory β
β create_object() β
βββββββ¬ββββββββ¬βββββ
β β
Type A Type B
β β
βΌ βΌ
βββββββββββ βββββββββββ
βProduct Aβ βProduct Bβ
βββββββββββ βββββββββββ
Python Implementation
# https://docs.python.org/3/library/abc.html
from abc import ABC, abstractmethod
# Abstract Product
class Vehicle(ABC):
@abstractmethod
def drive(self):
pass
# Concrete Products
class Car(Vehicle):
def drive(self):
return "π Driving a car on the road"
class Bike(Vehicle):
def drive(self):
return "ποΈ Riding a bike on the highway"
class Boat(Vehicle):
def drive(self):
return "π€ Sailing a boat on water"
# Factory
class VehicleFactory:
@staticmethod
def create_vehicle(vehicle_type):
vehicles = {
"car": Car,
"bike": Bike,
"boat": Boat
}
vehicle_class = vehicles.get(vehicle_type.lower())
if vehicle_class:
return vehicle_class()
else:
raise ValueError(f"Unknown vehicle type: {vehicle_type}")
# Usage Example
if __name__ == "__main__":
factory = VehicleFactory()
# Create different vehicles
car = factory.create_vehicle("car")
bike = factory.create_vehicle("bike")
boat = factory.create_vehicle("boat")
print(car.drive())
print(bike.drive())
print(boat.drive())
Output:
π Driving a car on the road
ποΈ Riding a bike on the highway
π€ Sailing a boat on water
3. ποΈ Observer Pattern
Mind Map
Observer Pattern
|
+-----------------+-----------------+
| | |
Purpose Components Use Cases
| | |
Notify Changes Subject (Publisher) Event Systems
| Observers Newsletter
One-to-Many Update Method Stock Market
Relationship Notifications
Flow Diagram
ββββββββββββββββββ
β Subject β
β (Publisher) β
ββββββ¬ββββββββββββ
β maintains list
βΌ
βββββββββββββββββββββββ
β Observer List β
β [Obs1, Obs2, Obs3] β
ββββββββ¬βββββββββββββββ
β State Change
βΌ
notify()
β
βββββΌββββββββ
β β β
βΌ βΌ βΌ
βββββββββββββββββββββ
βObs1 ββObs2 ββObs3 β
βupdateβupdateβupdateβ
βββββββββββββββββββββ
Python Implementation
from abc import ABC, abstractmethod
# Observer Interface
class Observer(ABC):
@abstractmethod
def update(self, message):
pass
# Concrete Observers
class EmailSubscriber(Observer):
def __init__(self, name):
self.name = name
def update(self, message):
print(f"π§ {self.name} received email: {message}")
class SMSSubscriber(Observer):
def __init__(self, name):
self.name = name
def update(self, message):
print(f"π± {self.name} received SMS: {message}")
# Subject (Publisher)
class NewsPublisher:
def __init__(self):
self._subscribers = []
def subscribe(self, observer):
self._subscribers.append(observer)
print(f"β
{observer.name} subscribed")
def unsubscribe(self, observer):
self._subscribers.remove(observer)
print(f"β {observer.name} unsubscribed")
def notify(self, message):
print(f"\nπ’ Broadcasting: {message}")
for subscriber in self._subscribers:
subscriber.update(message)
# Usage Example
if __name__ == "__main__":
# Create publisher
news = NewsPublisher()
# Create subscribers
alice = EmailSubscriber("Alice")
bob = SMSSubscriber("Bob")
charlie = EmailSubscriber("Charlie")
# Subscribe
news.subscribe(alice)
news.subscribe(bob)
news.subscribe(charlie)
# Publish news
news.notify("Breaking: Python 4.0 Released!")
# Unsubscribe Bob
news.unsubscribe(bob)
# Publish again
news.notify("New Design Patterns Tutorial Available!")
Output:
β
Alice subscribed
β
Bob subscribed
β
Charlie subscribed
π’ Broadcasting: Breaking: Python 4.0 Released!
π§ Alice received email: Breaking: Python 4.0 Released!
π± Bob received SMS: Breaking: Python 4.0 Released!
π§ Charlie received email: Breaking: Python 4.0 Released!
β Bob unsubscribed
π’ Broadcasting: New Design Patterns Tutorial Available!
π§ Alice received email: New Design Patterns Tutorial Available!
π§ Charlie received email: New Design Patterns Tutorial Available!
4. π― Strategy Pattern
Mind Map
Strategy Pattern
|
+-----------------+-----------------+
| | |
Purpose Components Use Cases
| | |
Select Algorithm Context Class Payment Methods
| Strategy Interface Sorting Algos
At Runtime Concrete Strategies Compression
Interchangeable Navigation Routes
Flow Diagram
ββββββββββββββββ
β Context β
β β
β - strategy β
ββββββββ¬ββββββββ
β uses
βΌ
ββββββββββββββββββββββ
β Strategy Interfaceβ
β + execute() β
βββββββ¬βββββββββββββββ
β
βββββΌββββββββββββ
β β β
βΌ βΌ βΌ
ββββββββββ ββββββββββ ββββββββββ
βStrategyβ βStrategyβ βStrategyβ
β A β β B β β C β
ββββββββββ ββββββββββ ββββββββββ
Python Implementation
from abc import ABC, abstractmethod
# Strategy Interface
class PaymentStrategy(ABC):
@abstractmethod
def pay(self, amount):
pass
# Concrete Strategies
class CreditCardPayment(PaymentStrategy):
def __init__(self, card_number):
self.card_number = card_number
def pay(self, amount):
return f"π³ Paid ${amount} using Credit Card ending in {self.card_number[-4:]}"
class PayPalPayment(PaymentStrategy):
def __init__(self, email):
self.email = email
def pay(self, amount):
return f"π
ΏοΈ Paid ${amount} using PayPal account {self.email}"
class CryptoPayment(PaymentStrategy):
def __init__(self, wallet_address):
self.wallet_address = wallet_address
def pay(self, amount):
return f"βΏ Paid ${amount} using Crypto wallet {self.wallet_address[:8]}..."
# Context
class ShoppingCart:
def __init__(self):
self.items = []
self.payment_strategy = None
def add_item(self, item, price):
self.items.append((item, price))
print(f"Added {item}: ${price}")
def set_payment_strategy(self, strategy):
self.payment_strategy = strategy
def checkout(self):
total = sum(price for _, price in self.items)
print(f"\nπ Total: ${total}")
if self.payment_strategy:
result = self.payment_strategy.pay(total)
print(result)
print("β
Payment successful!")
else:
print("β No payment method selected")
# Usage Example
if __name__ == "__main__":
cart = ShoppingCart()
# Add items
cart.add_item("Laptop", 999)
cart.add_item("Mouse", 25)
cart.add_item("Keyboard", 75)
# Pay with Credit Card
print("\n--- Payment Method 1: Credit Card ---")
cart.set_payment_strategy(CreditCardPayment("1234-5678-9012-3456"))
cart.checkout()
# Change payment strategy to PayPal
print("\n--- Payment Method 2: PayPal ---")
cart.set_payment_strategy(PayPalPayment("user@example.com"))
cart.checkout()
# Change to Crypto
print("\n--- Payment Method 3: Crypto ---")
cart.set_payment_strategy(CryptoPayment("1A2B3C4D5E6F7G8H9I"))
cart.checkout()
Output:
Added Laptop: $999
Added Mouse: $25
Added Keyboard: $75
--- Payment Method 1: Credit Card ---
π Total: $1099
π³ Paid $1099 using Credit Card ending in 3456
β
Payment successful!
--- Payment Method 2: PayPal ---
π Total: $1099
π
ΏοΈ Paid $1099 using PayPal account user@example.com
β
Payment successful!
--- Payment Method 3: Crypto ---
π Total: $1099
βΏ Paid $1099 using Crypto wallet 1A2B3C4D...
β
Payment successful!
5. π Decorator Pattern
Mind Map
Decorator Pattern
|
+-----------------+-----------------+
| | |
Purpose Components Use Cases
| | |
Add Features Component Interface Adding Features
| Concrete Component Text Formatting
Dynamically Decorators Coffee Orders
Without Subclass Wrap Objects Middleware
Flow Diagram
ββββββββββββββββββββ
β Component β
β (Interface) β
ββββββββββ¬ββββββββββ
β
ββββββ΄βββββ
β β
βΌ βΌ
βββββββββββ ββββββββββββββββ
βConcrete β β Decorator β
βComponentβ β (wraps) β
βββββββββββ ββββββββ¬ββββββββ
β
ββββββββ΄βββββββ
β β
βΌ βΌ
ββββββββββββ ββββββββββββ
βDecorator β βDecorator β
β A β β B β
ββββββββββββ ββββββββββββ
Python Implementation
from abc import ABC, abstractmethod
# Component Interface
class Coffee(ABC):
@abstractmethod
def cost(self):
pass
@abstractmethod
def description(self):
pass
# Concrete Component
class SimpleCoffee(Coffee):
def cost(self):
return 5
def description(self):
return "Simple Coffee"
# Decorator Base
class CoffeeDecorator(Coffee):
def __init__(self, coffee):
self._coffee = coffee
def cost(self):
return self._coffee.cost()
def description(self):
return self._coffee.description()
# Concrete Decorators
class MilkDecorator(CoffeeDecorator):
def cost(self):
return self._coffee.cost() + 2
def description(self):
return f"{self._coffee.description()} + Milk"
class SugarDecorator(CoffeeDecorator):
def cost(self):
return self._coffee.cost() + 1
def description(self):
return f"{self._coffee.description()} + Sugar"
class VanillaDecorator(CoffeeDecorator):
def cost(self):
return self._coffee.cost() + 3
def description(self):
return f"{self._coffee.description()} + Vanilla"
class CaramelDecorator(CoffeeDecorator):
def cost(self):
return self._coffee.cost() + 2.5
def description(self):
return f"{self._coffee.description()} + Caramel"
# Usage Example
if __name__ == "__main__":
print("β COFFEE SHOP ORDERS\n")
# Order 1: Simple Coffee
coffee1 = SimpleCoffee()
print(f"Order 1: {coffee1.description()}")
print(f"Cost: ${coffee1.cost()}\n")
# Order 2: Coffee with Milk
coffee2 = MilkDecorator(SimpleCoffee())
print(f"Order 2: {coffee2.description()}")
print(f"Cost: ${coffee2.cost()}\n")
# Order 3: Coffee with Milk and Sugar
coffee3 = SugarDecorator(MilkDecorator(SimpleCoffee()))
print(f"Order 3: {coffee3.description()}")
print(f"Cost: ${coffee3.cost()}\n")
# Order 4: Fully Loaded Coffee
coffee4 = CaramelDecorator(
VanillaDecorator(
SugarDecorator(
MilkDecorator(SimpleCoffee())
)
)
)
print(f"Order 4: {coffee4.description()}")
print(f"Cost: ${coffee4.cost()}\n")
Output:
β COFFEE SHOP ORDERS
Order 1: Simple Coffee
Cost: $5
Order 2: Simple Coffee + Milk
Cost: $7
Order 3: Simple Coffee + Milk + Sugar
Cost: $8
Order 4: Simple Coffee + Milk + Sugar + Vanilla + Caramel
Cost: $13.5
π Design Patterns Comparison
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β DESIGN PATTERN TYPES β
βββββββββββββββββββ¬ββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββ€
β CREATIONAL β STRUCTURAL β BEHAVIORAL β
β (How created) β (How composed)β (How they interact) β
βββββββββββββββββββΌββββββββββββββββββΌββββββββββββββββββββββββββββββββββ€
β β’ Singleton β β’ Decorator β β’ Observer β
β β’ Factory β β’ Adapter β β’ Strategy β
β β’ Builder β β’ Facade β β’ Command β
β β’ Prototype β β’ Proxy β β’ State β
β β’ Abstract β β’ Bridge β β’ Chain of Responsibility β
β Factory β β’ Composite β β’ Template Method β
βββββββββββββββββββ΄ββββββββββββββββββ΄ββββββββββββββββββββββββββββββββββ
π― When to Use Which Pattern?
Problem β Recommended Pattern
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Need only one instance β Singleton
Create objects without β Factory
specifying exact class
Need to notify multiple β Observer
objects of changes
Want to swap algorithms β Strategy
at runtime
Add features without β Decorator
modifying existing code
π‘ Best Practices
- Don't Overuse: Not every problem needs a design pattern
- Keep It Simple: Start simple, add patterns when needed
- Understand the Problem: Choose patterns that fit your specific needs
- Combine Patterns: Patterns can work together (e.g., Singleton Factory)
- Document Your Choice: Explain why you chose a particular pattern
π Conclusion
Design patterns are powerful tools that help you write:
- β Maintainable code
- β Scalable applications
- β Reusable components
- β Testable systems
Start with these 5 patterns, practice implementing them, and gradually explore more complex patterns as your needs grow!
π Further Reading
- Book: "Design Patterns: Elements of Reusable Object-Oriented Software" (Gang of Four)
- Online: refactoring.guru/design-patterns
- Practice: Try implementing these patterns in your next project!
Happy Coding! πβ¨
Found this helpful? Share it with your fellow developers!
Top comments (0)