System Design - 2
📘 Strategy Design Pattern
Definition
The Strategy Design Pattern is a behavioral design pattern that allows you to define a family of algorithms, encapsulate them in separate classes, and make them interchangeable at runtime.
In simple terms:
👉 Instead of writing many if-else conditions, we separate algorithms into different classes and choose them dynamically.
🧠 Problem Without Strategy Pattern
Imagine a payment system.
You support:
Credit Card
PayPal
UPI
❌ Bad Design (Using if-else)
class PaymentProcessor:
def process_payment(self, payment_type, amount):
if payment_type == "credit":
print(f"Processing credit card payment of {amount}")
elif payment_type == "paypal":
print(f"Processing PayPal payment of {amount}")
elif payment_type == "upi":
print(f"Processing UPI payment of {amount}")
Problems
❌ Violates Open Closed Principle
❌ Hard to extend
❌ Too many conditions
❌ Difficult to maintain
If a new payment method comes → modify the class.
✅ Solution: Strategy Pattern
Instead of conditions:
Create different payment strategies
Use them interchangeably
📊 Structure of Strategy Pattern
Strategy pattern has 3 components.
1️⃣ Strategy (Interface)
Defines common behavior.
2️⃣ Concrete Strategies
Different implementations of the algorithm.
3️⃣ Context
Uses the strategy.
🏗️ Strategy Pattern Structure
Strategy
↑
-----------------
| | |
StrategyA StrategyB StrategyC
↑
Context
Context uses any strategy dynamically.
🐍 Python Implementation
Python uses duck typing, so we don't need strict interfaces.
Step 1: Strategy Interface
from abc import ABC, abstractmethod
class PaymentStrategy(ABC):
@abstractmethod
def pay(self, amount):
pass
This ensures every strategy implements pay().
Step 2: Concrete Strategies
Credit Card
class CreditCardPayment(PaymentStrategy):
def pay(self, amount):
print(f"Paid {amount} using Credit Card")
PayPal
class PaypalPayment(PaymentStrategy):
def pay(self, amount):
print(f"Paid {amount} using PayPal")
UPI
class UpiPayment(PaymentStrategy):
def pay(self, amount):
print(f"Paid {amount} using UPI")
Each class implements the same interface.
Step 3: Context Class
The context uses the strategy.
class PaymentContext:
def __init__(self, strategy: PaymentStrategy):
self.strategy = strategy
def execute_payment(self, amount):
self.strategy.pay(amount)
Context doesn't know how payment works.
It just calls the strategy.
Step 4: Using the Strategy
payment = PaymentContext(CreditCardPayment())
payment.execute_payment(1000)
Output
Paid 1000 using Credit Card
Switch strategy dynamically:
payment = PaymentContext(PaypalPayment())
payment.execute_payment(500)
Or change runtime:
context = PaymentContext(UpiPayment())
context.execute_payment(200)
🎯 Key Idea
The algorithm can change at runtime without modifying the context.
📊 Before vs After Strategy Pattern
| Without Strategy | With Strategy |
|---|---|
| Large if-else blocks | Separate classes |
| Hard to extend | Easily extendable |
| Violates OCP | Follows OCP |
| Hard to maintain | Clean and modular |
🧠 Real World Examples
1️⃣ Payment Systems
Different payment algorithms.
Credit Card
UPI
PayPal
NetBanking
2️⃣ Sorting Algorithms
Strategy could be:
QuickSort
MergeSort
HeapSort
3️⃣ Route Planning (Maps)
Car route
Walking route
Bike route
Public transport
Each route is a strategy.
4️⃣ Compression Algorithms
ZIP
RAR
7z
User chooses strategy.
🏗️ Real Python Example (Discount System)
Instead of this:
if user_type == "premium":
discount = 20
elif user_type == "regular":
discount = 10
Use strategies.
Strategy
class DiscountStrategy:
def apply_discount(self, price):
pass
Premium Discount
class PremiumDiscount(DiscountStrategy):
def apply_discount(self, price):
return price * 0.8
Regular Discount
class RegularDiscount(DiscountStrategy):
def apply_discount(self, price):
return price * 0.9
Context
class ShoppingCart:
def __init__(self, strategy):
self.strategy = strategy
def checkout(self, price):
return self.strategy.apply_discount(price)
Usage
cart = ShoppingCart(PremiumDiscount())
print(cart.checkout(1000))
Output
800
⭐ Advantages
1️⃣ Removes if-else chains
Cleaner code.
2️⃣ Follows Open Closed Principle
Add new algorithms without changing existing code.
3️⃣ Improves maintainability
Each strategy is isolated.
4️⃣ Runtime flexibility
Algorithm can change dynamically.
⚠️ Disadvantages
1️⃣ More classes
Many strategies → many classes.
2️⃣ Client must know strategies
User must choose correct strategy.
🧠 Strategy vs Simple Function
In Python sometimes people use functions instead of classes.
Example:
def credit_payment(amount):
print("Credit payment")
def paypal_payment(amount):
print("Paypal payment")
But Strategy pattern is better for large systems.
⭐ Interview Definition (Best Answer)
Strategy Pattern is a behavioral design pattern that defines a family of algorithms, encapsulates each algorithm in a separate class, and allows them to be interchangeable at runtime without modifying the client code.
🎯 When to Use Strategy Pattern
Use it when:
Many if-else conditions
Multiple algorithms performing same task
Algorithm needs runtime selection
📌 Quick Visualization
PaymentStrategy
↑
---------------------
| | |
Credit PayPal UPI
↑
PaymentContext
Context uses any strategy dynamically.
📘 Factory Design Pattern
Definition
The Factory Design Pattern is a creational design pattern that provides an interface for creating objects without specifying the exact class of object that will be created.
In simple terms:
👉 Instead of creating objects using new or direct class calls everywhere, we delegate object creation to a factory.
🧠 Problem Without Factory Pattern
Suppose we build a Notification System.
We support:
Email notification
SMS notification
Push notification
❌ Bad Design (Direct Object Creation)
class EmailNotification:
def send(self):
print("Sending Email")
class SMSNotification:
def send(self):
print("Sending SMS")
class PushNotification:
def send(self):
print("Sending Push Notification")
class NotificationService:
def notify(self, type):
if type == "email":
notification = EmailNotification()
elif type == "sms":
notification = SMSNotification()
elif type == "push":
notification = PushNotification()
notification.send()
Problems
❌ Too many if-else conditions
❌ Violates Open Closed Principle
❌ Tight coupling between service and classes
❌ Hard to extend
If we add WhatsApp notification, we must modify this class.
✅ Solution: Factory Pattern
Instead of creating objects directly:
Move object creation logic into a Factory class
📊 Structure of Factory Pattern
Factory pattern contains 3 main components.
1️⃣ Product (Interface)
Common interface for objects.
2️⃣ Concrete Products
Actual implementations.
3️⃣ Factory
Creates objects.
Architecture Diagram
Notification (Interface)
↑
-------------------------
| | |
Email SMS Push
↑
NotificationFactory
🐍 Python Implementation
Step 1: Product Interface
from abc import ABC, abstractmethod
class Notification(ABC):
@abstractmethod
def send(self):
pass
Step 2: Concrete Products
Email Notification
class EmailNotification(Notification):
def send(self):
print("Sending Email Notification")
SMS Notification
class SMSNotification(Notification):
def send(self):
print("Sending SMS Notification")
Push Notification
class PushNotification(Notification):
def send(self):
print("Sending Push Notification")
Step 3: Factory Class
class NotificationFactory:
@staticmethod
def create_notification(notification_type):
if notification_type == "email":
return EmailNotification()
elif notification_type == "sms":
return SMSNotification()
elif notification_type == "push":
return PushNotification()
else:
raise ValueError("Invalid notification type")
Step 4: Using the Factory
factory = NotificationFactory()
notification = factory.create_notification("email")
notification.send()
Output:
Sending Email Notification
🎯 Key Idea
Client does not know:
which class is being instantiated
It only knows the factory.
📊 Before vs After Factory Pattern
| Without Factory | With Factory |
|---|---|
| Object creation everywhere | Centralized creation |
| Tight coupling | Loose coupling |
| Hard to maintain | Easy to maintain |
| Many if-else | Clean abstraction |
🧠 Real-World Examples
1️⃣ Database Connection Factory
A backend system may support:
MySQL
PostgreSQL
MongoDB
Instead of writing:
db = MySQL()
Use a factory.
class DatabaseFactory:
def get_database(type):
if type == "mysql":
return MySQL()
if type == "postgres":
return Postgres()
2️⃣ Logger Factory
Many frameworks create loggers using factories.
Example:
logging.getLogger("app")
This is Factory Pattern.
3️⃣ Machine Learning Model Factory
Suppose we support:
RandomForest
XGBoost
NeuralNetwork
Factory chooses the model.
class ModelFactory:
def create_model(model_type):
if model_type == "rf":
return RandomForest()
elif model_type == "xgb":
return XGBoost()
4️⃣ Payment Gateway
Payment gateway may support:
Stripe
Razorpay
PayPal
Factory returns correct gateway.
⭐ Types of Factory Patterns
There are 3 variations.
1️⃣ Simple Factory
Most common version.
One class decides which object to create.
Example:
NotificationFactory
2️⃣ Factory Method
Creation logic moved to subclasses.
Structure:
Creator
↑
ConcreteCreator
Example:
Dialog
↑
WindowsDialog
MacDialog
3️⃣ Abstract Factory
Creates families of related objects.
Example:
GUIFactory
↑
WindowsFactory
MacFactory
Creates:
Button
Checkbox
Menu
🧠 Example of Abstract Factory
Suppose OS UI elements.
Interface
class Button:
def render(self):
pass
Windows Button
class WindowsButton(Button):
def render(self):
print("Windows Button")
Mac Button
class MacButton(Button):
def render(self):
print("Mac Button")
Factory
class GUIFactory:
def create_button(self):
pass
Concrete Factories
class WindowsFactory(GUIFactory):
def create_button(self):
return WindowsButton()
class MacFactory(GUIFactory):
def create_button(self):
return MacButton()
📊 Factory vs Strategy
| Feature | Factory | Strategy |
|---|---|---|
| Purpose | Object creation | Algorithm selection |
| Pattern Type | Creational | Behavioral |
| Used for | Creating objects | Choosing behavior |
Example:
Factory → Create payment gateway
Strategy → Choose payment algorithm
⭐ Advantages
1️⃣ Loose Coupling
Client doesn't know concrete classes.
2️⃣ Centralized Object Creation
All object creation logic in one place.
3️⃣ Easier to Extend
Add new classes easily.
4️⃣ Cleaner Code
Removes messy instantiation logic.
⚠️ Disadvantages
1️⃣ More classes
Sometimes over-engineering.
2️⃣ Extra abstraction
Simple cases may not need it.
🎯 Interview Definition
Best answer:
Factory Pattern is a creational design pattern that provides a centralized way to create objects while hiding the object creation logic from the client.
🧠 Real Interview Example
Suppose an interviewer asks:
Design a system that supports multiple payment gateways.
Correct approach:
PaymentGatewayFactory
→ StripeGateway
→ RazorpayGateway
→ PaypalGateway
Factory decides which object to create.
1️⃣ What is the Singleton Design Pattern?
The Singleton pattern ensures that a class has only ONE instance throughout the entire application and provides a global access point to it.
In simple words:
Only one object of the class can ever exist.
Example real-world scenarios:
| Example | Why Singleton |
|---|---|
| Database connection | Creating multiple connections wastes resources |
| Logging system | All modules should log through same logger |
| Configuration manager | Same configuration shared everywhere |
| Cache manager | Same cache used by whole system |
| Thread pool manager | One shared resource manager |
2️⃣ Why Do We Need Singleton?
Imagine this situation:
Web Application
|
|---- Module A -> DB connection
|---- Module B -> DB connection
|---- Module C -> DB connection
Without singleton:
DBConnection()
DBConnection()
DBConnection()
Three connections are created ❌
Problems:
• Resource waste
• Inconsistent state
• Hard to manage
Singleton ensures:
Module A ----\
Module B ----- DBConnection (same object)
Module C ----/
3️⃣ Basic Implementation Idea
Singleton requires 3 rules
1️⃣ Private instance variable
2️⃣ Prevent direct object creation
3️⃣ Provide method to access instance
4️⃣ Simple Singleton Implementation (Python)
Step 1 — Basic approach
class Singleton:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
Usage:
a = Singleton()
b = Singleton()
print(a is b) # True
Output:
True
Both variables reference the same object.
5️⃣ How It Works Internally
Python object creation happens in two steps:
1. __new__() -> creates object
2. __init__() -> initializes object
Singleton overrides __new__() to control object creation.
Flow:
Call Singleton()
|
v
__new__() called
|
Is instance None?
|
YES
|
Create object
|
Store in _instance
|
Return object
Next time:
Call Singleton()
|
v
_instance already exists
|
Return same object
6️⃣ Singleton With Initialization Problem
Example:
class Singleton:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self):
print("Initializing...")
Test:
a = Singleton()
b = Singleton()
Output:
Initializing...
Initializing...
⚠️ Problem:
__init__() runs every time object is accessed.
Solution:
7️⃣ Singleton With Initialization Control
class Singleton:
_instance = None
_initialized = False
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self):
if not self._initialized:
print("Initialized once")
self._initialized = True
Now:
a = Singleton()
b = Singleton()
Output
Initialized once
8️⃣ Thread Safe Singleton (Important for Interviews)
Problem:
In multi-threaded systems, two threads might create objects simultaneously.
Example race condition:
Thread 1: _instance is None
Thread 2: _instance is None
Both create objects ❌
Solution: Lock
import threading
class Singleton:
_instance = None
_lock = threading.Lock()
def __new__(cls):
with cls._lock:
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
Now safe for multithreading systems.
9️⃣ Pythonic Singleton (Decorator Approach)
Python allows cleaner implementation using decorators.
def singleton(cls):
instances = {}
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
Usage:
@singleton
class Database:
pass
Now:
a = Database()
b = Database()
Same instance returned.
🔟 Singleton Using Metaclass (Advanced)
Most production Python systems use this.
class SingletonMeta(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
Usage:
class Database(metaclass=SingletonMeta):
pass
Now:
db1 = Database()
db2 = Database()
print(db1 is db2)
Output:
True
1️⃣1️⃣ Real World Example
Logger Singleton
class Logger:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def log(self, msg):
print("LOG:", msg)
Usage:
logger1 = Logger()
logger2 = Logger()
logger1.log("Start")
logger2.log("Stop")
Both use same logger instance.
1️⃣2️⃣ Singleton Class Diagram (UML)
+------------------+
| Singleton |
+------------------+
| - _instance |
+------------------+
| + get_instance() |
| + operation() |
+------------------+
Key Idea:
Singleton
|
|-- private instance
|-- static access method
1️⃣3️⃣ Advantages of Singleton
1️⃣ Controlled access
Only one instance exists
2️⃣ Memory efficient
No duplicate objects.
3️⃣ Global state management
Useful for:
• config
• cache
• logging
4️⃣ Lazy initialization
Instance created only when needed.
1️⃣4️⃣ Disadvantages of Singleton
❌ 1. Global state
Hard to debug.
❌ 2. Difficult testing
Unit tests may share state.
❌ 3. Violates Single Responsibility Principle
Class manages:
• logic
• instance lifecycle
❌ 4. Hidden dependencies
Modules rely on global instance.
1️⃣5️⃣ When Should You Use Singleton?
Good use cases:
✔ Database connection pool
✔ Logger
✔ Configuration manager
✔ Cache manager
✔ Thread pool
Bad use cases:
❌ Business logic classes
❌ Data models
❌ User sessions
1️⃣6️⃣ Singleton vs Static Class
| Feature | Singleton | Static Class |
|---|---|---|
| Object created | Yes | No |
| State | Can maintain | Hard |
| Inheritance | Possible | No |
| Lazy loading | Yes | No |
1️⃣7️⃣ Singleton vs Dependency Injection
Modern architectures often prefer:
Dependency Injection
instead of Singleton.
Example:
App
|
|--- inject Database instance
Benefits:
• Testable
• Flexible
• Less global state
1️⃣8️⃣ Interview Explanation (Short Version)
If asked:
What is Singleton?
Answer:
Singleton is a creational design pattern that ensures a class has only one instance and provides a global access point to that instance. It is commonly used for shared resources such as database connections, logging systems, configuration managers, and caching mechanisms.
1️⃣9️⃣ Common Interview Follow-up Questions
Q1: How to implement Singleton in Python?
Answer:
Using
• __new__() override
• decorator
• metaclass
Q2: How to make Singleton thread safe?
Use locks.
Q3: Why is Singleton considered anti-pattern sometimes?
Because:
• introduces global state
• difficult testing
• tight coupling
2️⃣0️⃣ Interview Trick Question
a = Singleton()
b = Singleton()
a.value = 10
print(b.value)
Output:
10
Because both reference same object.
1️⃣ What is the Observer Design Pattern?
The Observer pattern defines a one-to-many dependency between objects so that when one object changes state, all its dependents are automatically notified and updated.
Simple definition:
One Subject publishes changes → multiple Observers receive updates automatically.
2️⃣ Real-World Examples
1️⃣ YouTube subscription
You subscribe to a channel.
When a new video is uploaded → all subscribers get notified.
YouTube Channel (Subject)
|
---------------------
| | |
User1 User2 User3
(Observer)
2️⃣ Stock market system
Stock Price (Subject)
|
--------------
| |
Mobile App Web Dashboard
When price changes → both update.
3️⃣ Weather station
WeatherStation
|
-------------------------
| | |
MobileApp Website LED Display
3️⃣ Key Components of Observer Pattern
1️⃣ Subject (Publisher)
Maintains list of observers.
Responsibilities:
Register observer
Remove observer
Notify observers
2️⃣ Observer (Subscriber)
Receives updates from subject.
Must implement:
update()
3️⃣ Concrete Subject
Actual implementation of subject.
Example:
WeatherStation
StockMarket
YouTubeChannel
4️⃣ Concrete Observer
Actual subscribers.
Example:
MobileApp
Website
UserSubscriber
4️⃣ UML Diagram
+------------------+
| Subject |
+------------------+
| +attach() |
| +detach() |
| +notify() |
+------------------+
▲
|
+------------------+
| ConcreteSubject |
+------------------+
| state |
+------------------+
+------------------+
| Observer |
+------------------+
| +update() |
+------------------+
▲
|
+------------------+
| ConcreteObserver |
+------------------+
5️⃣ Basic Flow
Observer subscribes
↓
Subject stores observer
↓
State changes in subject
↓
Subject calls notify()
↓
notify() calls update() on observers
↓
Observers update themselves
6️⃣ Python Implementation (Step by Step)
Step 1 — Create Subject
class Subject:
def __init__(self):
self.observers = []
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()
Step 2 — Create Observer Interface
class Observer:
def update(self):
pass
Step 3 — Concrete Subject
Example: Weather Station
class WeatherStation(Subject):
def __init__(self):
super().__init__()
self.temperature = None
def set_temperature(self, temp):
self.temperature = temp
self.notify()
Step 4 — Concrete Observers
class MobileApp(Observer):
def update(self):
print("Mobile App received weather update")
class Website(Observer):
def update(self):
print("Website updated weather info")
Step 5 — Use the System
weather = WeatherStation()
mobile = MobileApp()
web = Website()
weather.attach(mobile)
weather.attach(web)
weather.set_temperature(30)
Output
Mobile App received weather update
Website updated weather info
7️⃣ Improved Python Version (Passing Data)
Better design: send updated state.
observer.update(data)
Improved Code
class Subject:
def __init__(self):
self.observers = []
def attach(self, observer):
self.observers.append(observer)
def notify(self, data):
for observer in self.observers:
observer.update(data)
Observer:
class Observer:
def update(self, data):
pass
Concrete observers:
class MobileDisplay(Observer):
def update(self, temp):
print("Mobile temperature:", temp)
class LEDDisplay(Observer):
def update(self, temp):
print("LED temperature:", temp)
Subject:
class WeatherStation(Subject):
def set_temperature(self, temp):
self.notify(temp)
Usage:
weather = WeatherStation()
mobile = MobileDisplay()
led = LEDDisplay()
weather.attach(mobile)
weather.attach(led)
weather.set_temperature(32)
Output:
Mobile temperature: 32
LED temperature: 32
8️⃣ Real-World System Design Example
Notification System
Order Service (Subject)
|
--------------------------------
| | |
EmailService SMSService PushNotification
When order status changes:
notify(order)
Each service sends notification.
9️⃣ Loose Coupling (Most Important Benefit)
Without observer:
OrderService
|
|---- EmailService
|---- SMSService
|---- PushService
Tightly coupled ❌
With Observer:
OrderService
|
notify()
|
------------------------
| | |
Email SMS Push
Now:
services can be added/removed easily
system is extensible
🔟 Push vs Pull Model
Observer pattern has two variations.
1️⃣ Push Model
Subject pushes data.
observer.update(data)
Example:
notify(temp)
2️⃣ Pull Model
Observer fetches data.
observer.update(subject)
Observer calls:
subject.get_state()
1️⃣1️⃣ Event Systems Use Observer Pattern
Many frameworks internally use this pattern.
Examples:
GUI frameworks
Event listeners
Webhooks
Reactive systems
1️⃣2️⃣ Python Libraries That Use Observer Concept
Examples include:
Django signals
Flask event hooks
RxPy reactive programming
They all follow Observer principles.
1️⃣3️⃣ Advantages
1️⃣ Loose coupling
Subject doesn't know observer details.
2️⃣ Easy extension
Add new observers without modifying subject.
3️⃣ Supports broadcast communication
One event → many receivers.
1️⃣4️⃣ Disadvantages
❌ Memory leaks
Observers not removed properly.
❌ Performance issue
Many observers → slow notification.
❌ Hard debugging
Chain of notifications.
1️⃣5️⃣ Observer vs Pub-Sub
Often confused.
| Feature | Observer | Pub-Sub |
|---|---|---|
| Communication | Direct | Broker |
| Coupling | Slight | Fully decoupled |
| Middleware | No | Yes |
Example:
Observer:
WeatherStation -> Observers
Pub-Sub:
Publisher -> Message Broker -> Subscribers
Example systems:
Kafka
RabbitMQ
MQTT
1️⃣6️⃣ Interview Example Answer
If asked:
Explain Observer Pattern
Answer:
Observer is a behavioral design pattern that defines a one-to-many relationship between objects so that when one object changes state, all its dependent objects are notified automatically. It is commonly used in event-driven systems like notification services, stock price monitoring, and GUI event listeners.
1️⃣7️⃣ Classic Interview Example
Stock Price Monitoring System
StockMarket
|
-----------------------------
| | |
MobileApp TradingBot Dashboard
When price changes → all observers update.
1️⃣8️⃣ Advanced Python Implementation (Best Practice)
Using ABC module.
from abc import ABC, abstractmethod
Observer:
class Observer(ABC):
@abstractmethod
def update(self, data):
pass
Better for large systems.
1️⃣9️⃣ Key Interview Insight
Observer pattern is used heavily in:
event systems
reactive programming
UI frameworks
messaging systems
distributed systems
2️⃣0️⃣ Easy Way to Remember
Subject = Publisher
Observer = Subscriber
So it is basically:
Publish → Subscribe
1️⃣ What is the Decorator Design Pattern?
The Decorator Pattern is a structural design pattern that allows us to dynamically add new behavior to an object without modifying its original code.
Simple definition:
Decorator wraps an object and adds additional functionality without changing the original class.
2️⃣ Simple Real-World Example
Think of coffee customization ☕
Base coffee:
Coffee
Add extras:
Coffee + Milk
Coffee + Sugar
Coffee + Milk + Chocolate
Instead of creating many classes:
MilkCoffee
SugarCoffee
MilkSugarCoffee
ChocolateMilkCoffee
We wrap coffee with decorators.
3️⃣ Core Idea
Decorator uses composition instead of inheritance.
Instead of:
class MilkCoffee(Coffee)
We do:
MilkDecorator(Coffee)
So objects get wrapped layer by layer.
4️⃣ Structure of Decorator Pattern
There are 4 components.
1️⃣ Component
Base interface.
2️⃣ Concrete Component
Original object.
3️⃣ Decorator
Wrapper class.
4️⃣ Concrete Decorators
Add extra behavior.
5️⃣ UML Structure
Component
|
-----------------
| |
ConcreteComponent Decorator
|
-----------------
| |
MilkDecorator SugarDecorator
6️⃣ Coffee Example (Python Implementation)
Step 1 — Component Interface
class Coffee:
def cost(self):
pass
Step 2 — Concrete Component
class BasicCoffee(Coffee):
def cost(self):
return 5
Basic coffee costs ₹5.
Step 3 — Base Decorator
Decorator wraps coffee.
class CoffeeDecorator(Coffee):
def __init__(self, coffee):
self.coffee = coffee
Step 4 — Concrete Decorators
Milk Decorator
class MilkDecorator(CoffeeDecorator):
def cost(self):
return self.coffee.cost() + 2
Sugar Decorator
class SugarDecorator(CoffeeDecorator):
def cost(self):
return self.coffee.cost() + 1
Step 5 — Using Decorators
coffee = BasicCoffee()
coffee = MilkDecorator(coffee)
coffee = SugarDecorator(coffee)
print(coffee.cost())
Output
8
Calculation:
Basic coffee = 5
Milk = +2
Sugar = +1
Total = 8
7️⃣ Visual Representation
SugarDecorator
|
MilkDecorator
|
BasicCoffee
Each decorator wraps the previous object.
8️⃣ Why Not Use Inheritance?
Without decorator, classes explode.
Coffee
MilkCoffee
SugarCoffee
MilkSugarCoffee
MilkSugarChocolateCoffee
This is called class explosion problem.
Decorator solves it.
9️⃣ Key Concept: Runtime Behavior Change
Decorator allows dynamic behavior addition.
Example:
coffee = BasicCoffee()
coffee = MilkDecorator(coffee)
coffee = SugarDecorator(coffee)
You can change combinations at runtime.
🔟 Python Decorators (Language Feature)
Python has native decorator syntax.
@decorator
This is based on Decorator Pattern.
Example
Simple function
def greet():
print("Hello")
Decorator
def my_decorator(func):
def wrapper():
print("Before function")
func()
print("After function")
return wrapper
Apply decorator
@my_decorator
def greet():
print("Hello")
Running:
greet()
Output:
Before function
Hello
After function
Decorator added extra behavior.
1️⃣1️⃣ Equivalent Without @
Python converts this:
@my_decorator
def greet():
to:
greet = my_decorator(greet)
So the function gets wrapped.
1️⃣2️⃣ Real Python Decorators
Common decorators you already use.
@staticmethod
@staticmethod
def func()
@classmethod
@classmethod
def func()
@property
@property
def name()
All are decorator pattern implementations.
1️⃣3️⃣ Real Backend System Example
Logging decorator
Instead of adding logging everywhere.
Example:
def log_decorator(func):
def wrapper(*args, **kwargs):
print("Function called:", func.__name__)
result = func(*args, **kwargs)
print("Function finished")
return result
return wrapper
Usage:
@log_decorator
def add(a, b):
return a + b
Now logging is added automatically.
1️⃣4️⃣ Authorization Decorator (Real API Example)
Example in backend systems.
def require_auth(func):
def wrapper(user):
if not user["is_logged_in"]:
raise Exception("Unauthorized")
return func(user)
return wrapper
Usage:
@require_auth
def view_dashboard(user):
print("Dashboard data")
Decorator adds security layer.
1️⃣5️⃣ Where Decorator Pattern is Used
Decorator pattern appears in:
Web frameworks
Authentication decorators.
@login_required
Logging systems
@log_execution
Performance monitoring
@time_execution
Retry mechanisms
@retry
Caching
@lru_cache
Python example:
from functools import lru_cache
1️⃣6️⃣ Example: Timing Decorator
import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print("Execution time:", end - start)
return result
return wrapper
Usage:
@timer
def compute():
for i in range(1000000):
pass
1️⃣7️⃣ Advantages
1️⃣ Open Closed Principle
Classes can be extended without modification.
2️⃣ Flexible behavior addition
Add features dynamically.
3️⃣ Avoid subclass explosion
No need for many subclasses.
4️⃣ Layered functionality
Multiple decorators stack.
1️⃣8️⃣ Disadvantages
❌ Many small objects
Too many decorators can complicate debugging.
❌ Hard to trace execution
Decorators wrap multiple layers.
1️⃣9️⃣ Decorator vs Inheritance
| Feature | Decorator | Inheritance |
|---|---|---|
| Behavior change | Runtime | Compile time |
| Flexibility | High | Low |
| Class explosion | No | Yes |
| Coupling | Loose | Tight |
2️⃣0️⃣ Interview Answer (Best Definition)
Best short answer:
Decorator Pattern is a structural design pattern that allows behavior to be added dynamically to an object by wrapping it inside decorator classes, without modifying the original object.
2️⃣1️⃣ Very Important Interview Insight
Decorator is heavily used in:
Python frameworks
Django
Flask
FastAPI
Logging systems
Authentication layers
⭐ Easy Way to Remember
Decorator = Wrapper
It wraps objects to add features.
1️⃣ Command Design Pattern
What is Command Pattern?
The Command Pattern converts a request into an object so that the request can be:
executed later
queued
logged
undone
Simple definition:
Command pattern encapsulates a request as an object.
Real-World Example
TV Remote
Remote Button → Command → TV
When you press power button:
Remote → PowerCommand → TV.turn_on()
The remote does not know TV logic, it just executes the command.
Components of Command Pattern
1️⃣ Command Interface
Defines:
execute()
2️⃣ Concrete Command
Implements command logic.
Example:
TurnOnCommand
TurnOffCommand
3️⃣ Receiver
Actual object that performs action.
Example:
TV
Light
Fan
4️⃣ Invoker
Triggers the command.
Example:
RemoteControl
UML Structure
Invoker (Remote)
|
v
Command
|
---------------------
| |
TurnOnCommand TurnOffCommand
|
v
Receiver (TV)
Python Implementation
Receiver
class Light:
def turn_on(self):
print("Light is ON")
def turn_off(self):
print("Light is OFF")
Command Interface
class Command:
def execute(self):
pass
Concrete Commands
class LightOnCommand(Command):
def __init__(self, light):
self.light = light
def execute(self):
self.light.turn_on()
class LightOffCommand(Command):
def __init__(self, light):
self.light = light
def execute(self):
self.light.turn_off()
Invoker
class RemoteControl:
def __init__(self, command):
self.command = command
def press_button(self):
self.command.execute()
Usage
light = Light()
on_command = LightOnCommand(light)
remote = RemoteControl(on_command)
remote.press_button()
Output
Light is ON
Where Command Pattern Is Used
Common uses:
Undo/Redo systems
Task queues
Job scheduling
GUI button actions
Event systems
Example:
Database Migration Tool
Each migration = command.
2️⃣ Adapter Design Pattern
What is Adapter Pattern?
Adapter pattern converts one interface into another interface expected by the client.
Simple definition:
Adapter allows incompatible interfaces to work together.
Real-World Example
Phone Charger Adapter
Indian socket → Adapter → US charger
US Charger (110V)
|
Adapter
|
Indian Socket (220V)
Adapter converts interface.
Software Example
Suppose you built a payment system expecting:
process_payment()
But third-party API uses:
make_payment()
Adapter converts it.
UML Structure
Client
|
Target Interface
|
Adapter
|
Adaptee (existing system)
Python Implementation
Existing system (Adaptee)
class OldPaymentGateway:
def make_payment(self, amount):
print("Processing payment:", amount)
Target Interface
class PaymentProcessor:
def process_payment(self, amount):
pass
Adapter
class PaymentAdapter(PaymentProcessor):
def __init__(self, gateway):
self.gateway = gateway
def process_payment(self, amount):
self.gateway.make_payment(amount)
Usage
gateway = OldPaymentGateway()
adapter = PaymentAdapter(gateway)
adapter.process_payment(500)
Output
Processing payment: 500
Where Adapter Pattern Is Used
Very common in:
API integration
Legacy system integration
Database drivers
ML model wrappers
File format converters
Example:
Pandas → Spark adapter
TensorFlow → PyTorch adapter
3️⃣ Facade Design Pattern
What is Facade Pattern?
Facade pattern provides a simplified interface to a complex system.
Simple definition:
Facade hides complex subsystem logic and exposes a simple interface.
Real-World Example
Movie Watching
To watch movie:
You need to:
Turn on TV
Turn on speakers
Insert DVD
Dim lights
Facade simplifies:
watch_movie()
Structure
Client
|
Facade
|
--------------------------
| | |
Subsystem1 Subsystem2 Subsystem3
Python Example
Subsystems
class CPU:
def start(self):
print("CPU started")
class Memory:
def load(self):
print("Memory loaded")
class HardDrive:
def read(self):
print("Hard drive reading")
Facade
class ComputerFacade:
def __init__(self):
self.cpu = CPU()
self.memory = Memory()
self.hard_drive = HardDrive()
def start_computer(self):
self.cpu.start()
self.memory.load()
self.hard_drive.read()
Usage
computer = ComputerFacade()
computer.start_computer()
Output
CPU started
Memory loaded
Hard drive reading
Client only interacts with facade, not subsystems.
Comparison of the Three Patterns
| Pattern | Purpose | Key Idea |
|---|---|---|
| Command | Encapsulate requests as objects | Decouple sender & receiver |
| Adapter | Convert one interface to another | Make incompatible systems work |
| Facade | Simplify complex system | Provide simple interface |
Visual Comparison
Command
Button → Command → Receiver
Adapter
Client → Adapter → Old System
Facade
Client → Facade → Complex System
Interview Tip (Very Important)
If asked in interview:
Command Pattern
Used for:
undo/redo
task queues
event systems
Adapter Pattern
Used when:
integrating third-party APIs
converting incompatible interfaces
Facade Pattern
Used when:
hiding complex subsystems
simplifying large APIs
Python Real-World Examples
Command
Celery task queues
Adapter
Database adapters
Example:
psycopg2 adapter
Facade
Framework APIs often act as facades.
Example:
Django ORM
It hides SQL complexity.
1️⃣ Composite Design Pattern
What is Composite Pattern?
The Composite Pattern allows you to treat individual objects and groups of objects in the same way.
Simple definition:
Composite pattern composes objects into tree structures so that clients can treat individual objects and compositions uniformly.
Real World Example
File System
Folder
├── File
├── File
└── Folder
├── File
└── File
Operations like:
open()
delete()
size()
should work on:
single file
folder containing files
Both treated uniformly.
Structure
Three components:
1️⃣ Component
Common interface.
2️⃣ Leaf
Individual object.
3️⃣ Composite
Object containing children.
UML Structure
Component
/ \
Leaf Composite
|
List of Components
Python Implementation
Component Interface
class FileSystemComponent:
def show(self):
pass
Leaf (File)
class File(FileSystemComponent):
def __init__(self, name):
self.name = name
def show(self):
print("File:", self.name)
Composite (Folder)
class Folder(FileSystemComponent):
def __init__(self, name):
self.name = name
self.children = []
def add(self, component):
self.children.append(component)
def show(self):
print("Folder:", self.name)
for child in self.children:
child.show()
Usage
file1 = File("resume.pdf")
file2 = File("photo.jpg")
folder = Folder("Documents")
folder.add(file1)
folder.add(file2)
folder.show()
Output
Folder: Documents
File: resume.pdf
File: photo.jpg
Key Concept
Client does not need to know whether it's:
File
Folder
Both are handled the same way.
Where Composite Pattern Is Used
Common uses:
File systems
UI components (buttons, panels)
Organization hierarchy
JSON/XML trees
AST (Abstract Syntax Trees)
Example in ML:
Neural Network
├ Layer
├ Layer
└ Layer
2️⃣ Template Method Pattern
What is Template Method Pattern?
Template pattern defines the skeleton of an algorithm in a base class, but allows subclasses to override certain steps.
Simple definition:
Template Method defines the structure of an algorithm while allowing subclasses to redefine specific steps.
Real World Example
Making Tea vs Coffee
Steps:
Boil Water
Add Main Ingredient
Pour in Cup
Add Extras
Tea vs Coffee only change:
Add ingredient
Add extras
But overall process is same.
Structure
Abstract Class
|
Template Method
|
-----------------------
| | |
Step1 Step2 Step3
Some steps:
fixed
overridden
Python Implementation
Base Class
from abc import ABC, abstractmethod
class Beverage(ABC):
def make_beverage(self):
self.boil_water()
self.add_main()
self.pour_in_cup()
self.add_extras()
def boil_water(self):
print("Boiling water")
def pour_in_cup(self):
print("Pouring into cup")
@abstractmethod
def add_main(self):
pass
@abstractmethod
def add_extras(self):
pass
Tea Implementation
class Tea(Beverage):
def add_main(self):
print("Adding tea leaves")
def add_extras(self):
print("Adding lemon")
Coffee Implementation
class Coffee(Beverage):
def add_main(self):
print("Adding coffee powder")
def add_extras(self):
print("Adding sugar and milk")
Usage
tea = Tea()
tea.make_beverage()
Output
Boiling water
Adding tea leaves
Pouring into cup
Adding lemon
Key Idea
The algorithm flow is fixed, but steps can change.
Where Template Pattern Is Used
Very common in:
ML pipelines
Training frameworks
Data processing pipelines
Web frameworks
Example:
Train Model
├ Load Data
├ Preprocess
├ Train
└ Evaluate
Different models override training step.
3️⃣ Proxy Design Pattern
What is Proxy Pattern?
Proxy provides a placeholder or surrogate for another object to control access to it.
Simple definition:
Proxy controls access to an object.
Real World Example
Credit Card Payment
You don't directly access:
Bank Server
Instead:
Credit Card → Proxy → Bank
Proxy performs:
validation
security
logging
Types of Proxy
Common proxies:
1️⃣ Virtual Proxy
Lazy loading.
Example:
Load large image only when needed
2️⃣ Protection Proxy
Access control.
Example:
Admin access check
3️⃣ Remote Proxy
Object on remote server.
Example:
RPC / API calls
Structure
Client
|
Proxy
|
Real Object
Python Implementation
Real Object
class Database:
def query(self):
print("Executing database query")
Proxy
class DatabaseProxy:
def __init__(self, user):
self.user = user
self.database = Database()
def query(self):
if self.user != "admin":
print("Access denied")
else:
self.database.query()
Usage
proxy = DatabaseProxy("guest")
proxy.query()
Output
Access denied
Admin access:
proxy = DatabaseProxy("admin")
proxy.query()
Output
Executing database query
Where Proxy Pattern Is Used
Very common in:
Security
Authentication proxies.
Lazy Loading
Example:
Load ML model only when used
API Gateways
Gateway acts as proxy.
ORM frameworks
Example:
Lazy loading in ORM:
Django ORM
SQLAlchemy
Comparison of the Three Patterns
| Pattern | Type | Purpose |
|---|---|---|
| Composite | Structural | Treat objects and groups uniformly |
| Template | Behavioral | Define algorithm structure |
| Proxy | Structural | Control access to object |
Visual Summary
Composite
Tree structure
Folder
├ File
└ Folder
Template
Fixed algorithm
Step1
Step2 (override)
Step3 (override)
Proxy
Client → Proxy → Real Object
Interview Tip (Very Important)
When asked:
Composite
Use when system has tree structures.
Examples:
file systems
UI hierarchies
neural network layers
Template
Use when algorithm flow is fixed but steps vary.
Examples:
ML training pipeline
ETL pipelines
data processing
Proxy
Use when we want controlled access.
Examples:
caching
authentication
lazy loading
API gateway
✅ Now you have covered most of the major LLD interview design patterns.
You have now learned:
Creational
Factory
Singleton
Structural
Adapter
Facade
Decorator
Composite
Proxy
Behavioral
Strategy
Observer
Command
Template
1️⃣ Chain of Responsibility Pattern
What is Chain of Responsibility?
This pattern allows multiple objects to handle a request, and the request passes through a chain of handlers until one handles it.
Simple definition:
A request is passed along a chain of handlers until one processes it.
Real-World Example
Customer Support System
Customer Request
|
v
Level 1 Support
|
v
Level 2 Support
|
v
Manager
If Level 1 cannot solve, it forwards to Level 2.
Structure
Client → Handler → Handler → Handler
Each handler decides:
handle request
pass to next handler
Python Example
Handler Base Class
class Handler:
def __init__(self, next_handler=None):
self.next_handler = next_handler
def handle(self, request):
if self.next_handler:
self.next_handler.handle(request)
Concrete Handlers
class Level1Support(Handler):
def handle(self, request):
if request == "basic":
print("Level 1 solved the issue")
else:
super().handle(request)
class Level2Support(Handler):
def handle(self, request):
if request == "intermediate":
print("Level 2 solved the issue")
else:
super().handle(request)
class Manager(Handler):
def handle(self, request):
print("Manager solved the issue")
Usage
manager = Manager()
level2 = Level2Support(manager)
level1 = Level1Support(level2)
level1.handle("intermediate")
Output
Level 2 solved the issue
Where It Is Used
Very common in:
Middleware pipelines
Logging frameworks
Request filtering
Event processing
Example:
Web Request
→ Authentication
→ Authorization
→ Validation
→ Controller
2️⃣ Bridge Design Pattern
What is Bridge Pattern?
Bridge pattern separates abstraction from implementation so they can evolve independently.
Simple definition:
Decouple abstraction and implementation.
Real World Example
Remote Control and Devices
Remote types:
Basic Remote
Advanced Remote
Devices:
TV
Radio
Without bridge:
BasicTVRemote
AdvancedTVRemote
BasicRadioRemote
AdvancedRadioRemote
Too many classes ❌
Bridge solves this.
Structure
Abstraction (Remote)
|
v
Implementation (Device)
Python Example
Implementation Interface
class Device:
def turn_on(self):
pass
def turn_off(self):
pass
Concrete Devices
class TV(Device):
def turn_on(self):
print("TV turned on")
def turn_off(self):
print("TV turned off")
class Radio(Device):
def turn_on(self):
print("Radio turned on")
def turn_off(self):
print("Radio turned off")
Abstraction
class RemoteControl:
def __init__(self, device):
self.device = device
def turn_on(self):
self.device.turn_on()
def turn_off(self):
self.device.turn_off()
Usage
tv = TV()
remote = RemoteControl(tv)
remote.turn_on()
Output
TV turned on
Where It Is Used
Common uses:
UI frameworks
Device drivers
cross-platform applications
Example:
Database abstraction
One abstraction works with:
MySQL
PostgreSQL
MongoDB
3️⃣ Builder Design Pattern
What is Builder Pattern?
Builder pattern constructs complex objects step by step.
Simple definition:
Separate construction of an object from its representation.
Real-World Example
Building a House
Steps:
build foundation
build walls
build roof
Different houses:
Wooden house
Concrete house
Glass house
Same steps, different implementations.
Structure
Director → Builder → Concrete Builder → Product
Python Example
Product
class House:
def __init__(self):
self.parts = []
def add(self, part):
self.parts.append(part)
def show(self):
print(self.parts)
Builder
class HouseBuilder:
def __init__(self):
self.house = House()
def build_walls(self):
self.house.add("Walls")
def build_roof(self):
self.house.add("Roof")
def get_house(self):
return self.house
Director
class Director:
def construct(self, builder):
builder.build_walls()
builder.build_roof()
Usage
builder = HouseBuilder()
director = Director()
director.construct(builder)
house = builder.get_house()
house.show()
Output
['Walls', 'Roof']
Where It Is Used
Very common in:
complex object creation
configuration objects
ML pipelines
query builders
Example:
HTTP Request Builder
4️⃣ Iterator Pattern
What is Iterator Pattern?
Iterator provides a way to traverse a collection without exposing its internal structure.
Simple definition:
Access elements sequentially without exposing representation.
Real-World Example
You can iterate over:
list
set
dictionary
using the same pattern:
for item in collection
Python Example
Python already implements iterator.
Example:
numbers = [1,2,3,4]
for num in numbers:
print(num)
But custom iterator example:
Custom Iterator
class MyIterator:
def __init__(self, data):
self.data = data
self.index = 0
def __next__(self):
if self.index >= len(self.data):
raise StopIteration
value = self.data[self.index]
self.index += 1
return value
Iterable
class MyCollection:
def __init__(self, data):
self.data = data
def __iter__(self):
return MyIterator(self.data)
Usage
collection = MyCollection([10,20,30])
for item in collection:
print(item)
Where It Is Used
Everywhere in Python:
lists
sets
generators
database cursors
5️⃣ Flyweight Pattern
What is Flyweight?
Flyweight reduces memory usage by sharing objects.
Simple definition:
Share common objects instead of creating duplicates.
Real-World Example
Text Editor
If a document has:
1000 letter "A"
Instead of storing 1000 objects, store one shared object.
Structure
Client
|
Flyweight Factory
|
Shared Objects
Python Example
Flyweight Class
class Character:
def __init__(self, char):
self.char = char
def display(self):
print(self.char)
Flyweight Factory
class CharacterFactory:
_characters = {}
@classmethod
def get_character(cls, char):
if char not in cls._characters:
cls._characters[char] = Character(char)
return cls._characters[char]
Usage
a1 = CharacterFactory.get_character("A")
a2 = CharacterFactory.get_character("A")
print(a1 is a2)
Output
True
Memory reused.
Where It Is Used
Common in:
game engines
text editors
caching systems
object pools
6️⃣ State Design Pattern
What is State Pattern?
State pattern allows an object to change its behavior when its state changes.
Simple definition:
Object behavior changes based on internal state.
Real-World Example
Traffic Light
States:
Red
Yellow
Green
Each state behaves differently.
Structure
Context
|
State Interface
|
------------------
| | |
Red Yellow Green
Python Example
State Interface
class State:
def handle(self):
pass
Concrete States
class RedState(State):
def handle(self):
print("Stop")
class GreenState(State):
def handle(self):
print("Go")
Context
class TrafficLight:
def __init__(self, state):
self.state = state
def change(self, state):
self.state = state
def action(self):
self.state.handle()
Usage
light = TrafficLight(RedState())
light.action()
light.change(GreenState())
light.action()
Output
Stop
Go
Quick Comparison
| Pattern | Purpose |
|---|---|
| Chain of Responsibility | Pass request through chain |
| Bridge | Separate abstraction and implementation |
| Builder | Construct complex objects step-by-step |
| Iterator | Traverse collections |
| Flyweight | Share objects to save memory |
| State | Change behavior based on state |
1. Mediator Design Pattern
Definition
The Mediator Pattern reduces direct communication between objects by introducing a mediator object that handles interaction.
Instead of objects talking to each other directly, they communicate through a central mediator.
Problem Without Mediator
If multiple objects communicate with each other directly:
User1 <-> User2
User1 <-> User3
User2 <-> User3
User2 <-> User4
This creates tight coupling and becomes complex.
With Mediator
ChatMediator
/ | \
User1 User2 User3
All communication goes through the mediator.
Real-world Example
Chatroom system:
Users send messages to chat mediator, which broadcasts to others.
Python Implementation
Step 1: Mediator Interface
class ChatMediator:
def send_message(self, message, user):
pass
Step 2: Concrete Mediator
class ChatRoom(ChatMediator):
def __init__(self):
self.users = []
def add_user(self, user):
self.users.append(user)
def send_message(self, message, sender):
for user in self.users:
if user != sender:
user.receive(message)
Step 3: Colleague (User)
class User:
def __init__(self, name, mediator):
self.name = name
self.mediator = mediator
def send(self, message):
print(f"{self.name} sends: {message}")
self.mediator.send_message(message, self)
def receive(self, message):
print(f"{self.name} receives: {message}")
Step 4: Client
chatroom = ChatRoom()
u1 = User("Sanjay", chatroom)
u2 = User("Rahul", chatroom)
u3 = User("Amit", chatroom)
chatroom.add_user(u1)
chatroom.add_user(u2)
chatroom.add_user(u3)
u1.send("Hello everyone!")
Advantages
✔ Reduces coupling
✔ Centralized communication logic
✔ Easy to modify interactions
Real-world Usage
Air traffic control
Chat systems
GUI event systems
2. Prototype Design Pattern
Definition
Prototype pattern creates new objects by copying an existing object instead of creating from scratch.
It uses cloning.
Problem
Creating objects can be expensive:
Example:
Loading ML model
Loading game character
Loading large config
Instead of rebuilding → clone existing object.
Real-world Example
Game character spawning.
Original Monster → Clone → Clone → Clone
Python Implementation
Python supports cloning via copy module.
Basic Example
import copy
class Car:
def __init__(self, model, color):
self.model = model
self.color = color
def clone(self):
return copy.deepcopy(self)
Usage
car1 = Car("Tesla", "Red")
car2 = car1.clone()
car2.color = "Blue"
print(car1.color)
print(car2.color)
Output
Red
Blue
Shallow vs Deep Copy
Shallow Copy
Copies reference.
copy.copy()
Deep Copy
Copies entire object graph.
copy.deepcopy()
Advantages
✔ Faster object creation
✔ Avoid expensive initialization
✔ Useful for complex objects
Real-world Usage
Game development
ML model cloning
Configuration templates
3. Visitor Design Pattern
Definition
Visitor pattern allows adding new operations to objects without modifying their classes.
It separates data structure from operations.
Problem
Suppose we have shapes:
Circle
Rectangle
Triangle
Now we want operations:
Area
Perimeter
Draw
Export
Without visitor → modify every class.
Visitor → add operation separately.
Structure
Visitor
|
ConcreteVisitor
Element
|
ConcreteElement
Python Example
Step 1: Visitor
class Visitor:
def visit_circle(self, circle):
pass
def visit_rectangle(self, rectangle):
pass
Step 2: Concrete Visitor
class AreaVisitor(Visitor):
def visit_circle(self, circle):
return 3.14 * circle.radius ** 2
def visit_rectangle(self, rectangle):
return rectangle.width * rectangle.height
Step 3: Elements
class Circle:
def __init__(self, radius):
self.radius = radius
def accept(self, visitor):
return visitor.visit_circle(self)
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def accept(self, visitor):
return visitor.visit_rectangle(self)
Usage
circle = Circle(5)
rect = Rectangle(4,6)
visitor = AreaVisitor()
print(circle.accept(visitor))
print(rect.accept(visitor))
Advantages
✔ Add operations without changing classes
✔ Good for complex object structures
Disadvantages
❌ Harder to understand
❌ Difficult if classes change frequently
Real-world Use
Compilers
AST processing
Data processing pipelines
4. Memento Design Pattern
Definition
Memento pattern saves object state so it can be restored later.
Used for undo/redo functionality.
Structure
Originator → creates state
Memento → stores state
Caretaker → manages history
Example
Text editor undo feature.
State1 → State2 → State3
Undo → State2
Undo → State1
Python Implementation
Memento
class Memento:
def __init__(self, state):
self.state = state
Originator
class Editor:
def __init__(self):
self.content = ""
def write(self, text):
self.content += text
def save(self):
return Memento(self.content)
def restore(self, memento):
self.content = memento.state
Caretaker
class History:
def __init__(self):
self.states = []
def push(self, memento):
self.states.append(memento)
def pop(self):
return self.states.pop()
Usage
editor = Editor()
history = History()
editor.write("Hello ")
history.push(editor.save())
editor.write("World")
history.push(editor.save())
editor.write("!!!")
editor.restore(history.pop())
print(editor.content)
Advantages
✔ Supports undo/redo
✔ Encapsulates object state
Real-world Usage
Text editors
Game checkpoints
Database transactions
5. Null Object Pattern
Definition
Instead of returning None, return an object with default behavior.
This removes the need for null checks.
Problem
Without Null Object:
if user is not None:
user.send_message()
With Null Object → no checks.
Example
Abstract Class
class Customer:
def get_name(self):
pass
Real Object
class RealCustomer(Customer):
def __init__(self, name):
self.name = name
def get_name(self):
return self.name
Null Object
class NullCustomer(Customer):
def get_name(self):
return "Guest"
Usage
def get_customer(name):
if name == "Sanjay":
return RealCustomer(name)
return NullCustomer()
customer = get_customer("Unknown")
print(customer.get_name())
Output
Guest
Advantages
✔ Removes null checks
✔ Cleaner code
✔ Prevents runtime errors
Quick Summary
| Pattern | Purpose |
|---|---|
| Mediator | Centralize communication |
| Prototype | Clone objects |
| Visitor | Add operations without modifying classes |
| Memento | Save & restore state |
| Null Object | Replace None with default behavior |
Comments
Post a Comment