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 StrategyWith Strategy
Large if-else blocksSeparate classes
Hard to extendEasily extendable
Violates OCPFollows OCP
Hard to maintainClean 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 FactoryWith Factory
Object creation everywhereCentralized creation
Tight couplingLoose coupling
Hard to maintainEasy to maintain
Many if-elseClean 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

FeatureFactoryStrategy
PurposeObject creationAlgorithm selection
Pattern TypeCreationalBehavioral
Used forCreating objectsChoosing 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:

ExampleWhy Singleton
Database connectionCreating multiple connections wastes resources
Logging systemAll modules should log through same logger
Configuration managerSame configuration shared everywhere
Cache managerSame cache used by whole system
Thread pool managerOne 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

FeatureSingletonStatic Class
Object createdYesNo
StateCan maintainHard
InheritancePossibleNo
Lazy loadingYesNo

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.

FeatureObserverPub-Sub
CommunicationDirectBroker
CouplingSlightFully decoupled
MiddlewareNoYes

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

FeatureDecoratorInheritance
Behavior changeRuntimeCompile time
FlexibilityHighLow
Class explosionNoYes
CouplingLooseTight

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

PatternPurposeKey Idea
CommandEncapsulate requests as objectsDecouple sender & receiver
AdapterConvert one interface to anotherMake incompatible systems work
FacadeSimplify complex systemProvide 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

PatternTypePurpose
CompositeStructuralTreat objects and groups uniformly
TemplateBehavioralDefine algorithm structure
ProxyStructuralControl 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

PatternPurpose
Chain of ResponsibilityPass request through chain
BridgeSeparate abstraction and implementation
BuilderConstruct complex objects step-by-step
IteratorTraverse collections
FlyweightShare objects to save memory
StateChange 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

PatternPurpose
MediatorCentralize communication
PrototypeClone objects
VisitorAdd operations without modifying classes
MementoSave & restore state
Null ObjectReplace None with default behavior





Comments

Popular posts from this blog

Resume Work and Project Details

Time Series and MMM basics

LINEAR REGRESSION