Python26 min read
Python Design Patterns
Understand common design patterns so you can structure code cleanly, avoid messy spaghetti logic, and build scalable systems like real applications.
David Miller
August 27, 2025
9.8k207
A design pattern is a proven solution to a common coding problem.
Important:
Patterns are not “rules”. They are tools.
Use a pattern only when it makes the code simpler and clearer.
## When patterns help most
- when code is growing
- when many developers work together
- when you need flexibility and extension
- when you want cleaner architecture
## 1) Singleton Pattern (one instance only)
Purpose:
- ensure only one object exists (like a DB connection manager)
```python
class Database:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
db1 = Database()
db2 = Database()
print(db1 is db2) # True
```
Benefit:
- shared global state safely (but use carefully)
## 2) Factory Pattern (create objects based on input)
Purpose:
- create different objects without repeating if/else everywhere
```python
class Dog:
def speak(self):
return "Woof!"
class Cat:
def speak(self):
return "Meow!"
class AnimalFactory:
@staticmethod
def create(animal_type):
if animal_type == "dog":
return Dog()
if animal_type == "cat":
return Cat()
raise ValueError("Unknown animal")
animal = AnimalFactory.create("dog")
print(animal.speak())
```
Benefit:
- easy to add new types later
## 3) Observer Pattern (notify subscribers)
Purpose:
- when many parts need updates after one change
Examples:
- UI updates
- notifications
- event systems
```python
class Subject:
def __init__(self):
self._observers = []
def attach(self, observer):
self._observers.append(observer)
def notify(self, message):
for obs in self._observers:
obs.update(message)
class Observer:
def __init__(self, name):
self.name = name
def update(self, message):
print(f"{self.name} received: {message}")
subject = Subject()
subject.attach(Observer("User1"))
subject.attach(Observer("User2"))
subject.notify("New update!")
```
Benefit:
- loose coupling (components don’t directly depend on each other)
## 4) Strategy Pattern (swap behavior at runtime)
Purpose:
- choose an algorithm/behavior dynamically
Example:
- multiple payment methods
```python
class CreditCard:
def pay(self, amount):
print(f"Paid ${amount} with credit card")
class PayPal:
def pay(self, amount):
print(f"Paid ${amount} with PayPal")
class Cart:
def __init__(self, strategy):
self.strategy = strategy
def checkout(self, amount):
self.strategy.pay(amount)
cart = Cart(CreditCard())
cart.checkout(100)
cart = Cart(PayPal())
cart.checkout(50)
```
Benefit:
- no big if/else inside your checkout logic
## Graph: how patterns reduce complexity
```mermaid
flowchart TD
A[Problem grows] --> B[Many if/else and duplicated logic]
B --> C[Hard to maintain]
A --> D[Use pattern where it fits]
D --> E[Cleaner structure]
E --> F[Easier extension]
```
## Remember
- Patterns are reusable solutions
- Use when it improves readability and scaling
- Don’t force patterns into small/simple code
#Python#Advanced#Design Patterns