## Singleton
Singleton ensures only one instance of a class exists during the entire runtime, and it provides a global access point to it.

```python
class Singleton:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

Thread Safe Singleton

import threading
 
class Singleton:
    _instance = None
    _lock = threading.Lock()
 
    def __new__(cls):
        if cls._instance is None:
            with cls._lock:
                if cls._instance is None:  # double-checked locking
                    cls._instance = super().__new__(cls)
        return cls._instance
 

Singleton - An anti-pattern

“Singleton introduces global state, makes testing difficult, creates tight coupling, and hides dependencies. That’s why it’s often considered an anti-pattern in large systems.”

Factory

Factory Pattern creates objects without exposing the creation logic to the client. It helps avoid if-else or switch cases when deciding which object to create.

type = "upi"
 
if type == "upi":
    obj = UpiPayment()
elif type == "card":
    obj = CardPayment()
elif type == "paypal":
    obj = PaypalPayment()
 
from abc import ABC, abstractmethod
 
class Payment(ABC):
    @abstractmethod
    def pay(self, amount):
        pass
 
class UpiPayment(Payment):
    def pay(self, amount):
        print(f"paid {amount} via UPI")
 
class CardPayment(Payment):
    def pay(self, amount):
        print(f"paid {amount} via Card")
 
class PaypalPayment(Payment):
    def pay(self, amount):
        print(f"paid {amount} via PayPal")
 
class PaymentFactory:
    @staticmethod
    def get_payment(method: str) -> Payment:
        if method == "upi":
            return UpiPayment()
        elif method == "card":
            return CardPayment()
        elif method == "paypal":
            return PaypalPayment()
        else:
            raise ValueError("invalid payment method")
 
payment = PaymentFactory.get_payment("upi")
payment.pay(500)
 

Factory Pattern encapsulates object creation and avoids large if-else blocks.
I pass a type to the factory, and it returns the correct object.

Examples

  • Python’s logging.getLogger() is a factory
  • Database driver selection
  • Serializer factory (JSON, XML, YAML)

Abstract Factory

Factory Method creates a single object.
Abstract Factory creates a family of related objects without specifying their concrete classes. Example: Dark theme → dark button + dark checkbox.

from abc import ABC, abstractmethod
 
# ---- Product Interfaces ----
 
class Button(ABC):
    @abstractmethod
    def render(self):
        pass
 
class Checkbox(ABC):
    @abstractmethod
    def render(self):
        pass
 
 
# ---- Concrete Products ----
 
class DarkButton(Button):
    def render(self):
        print("dark button")
 
class LightButton(Button):
    def render(self):
        print("light button")
 
class DarkCheckbox(Checkbox):
    def render(self):
        print("dark checkbox")
 
class LightCheckbox(Checkbox):
    def render(self):
        print("light checkbox")
 
 
# ---- Abstract Factory ----
 
class UIThemeFactory(ABC):
    @abstractmethod
    def create_button(self) -> Button:
        pass
 
    @abstractmethod
    def create_checkbox(self) -> Checkbox:
        pass
 
 
# ---- Concrete Factories ----
 
class DarkThemeFactory(UIThemeFactory):
    def create_button(self):
        return DarkButton()
 
    def create_checkbox(self):
        return DarkCheckbox()
 
class LightThemeFactory(UIThemeFactory):
    def create_button(self):
        return LightButton()
 
    def create_checkbox(self):
        return LightCheckbox()
 
 
# ---- Client Code ----
 
def render_ui(factory: UIThemeFactory):
    button = factory.create_button()
    checkbox = factory.create_checkbox()
    button.render()
    checkbox.render()
 
 
# ---- Usage ----
if __name__ == "__main__":
    theme = DarkThemeFactory()
    render_ui(theme)
 
    theme = LightThemeFactory()
    render_ui(theme)
 

Abstract Factory guarantees that products created together are compatible.

Builder Pattern

Builder pattern constructs complex objects step by step and lets me set only the fields I care about. It’s useful when an object has many optional parameters or should be immutable.

Use Builder when the object has many optional fields or requires step-by-step construction.

from dataclasses import dataclass
 
@dataclass(frozen=True)
class User:
    name: str
    age: int | None = None
 
class UserBuilder:
    def __init__(self, name):
        self.name = name
        self.age = None   # mutable
 
    def set_age(self, age):
        self.age = age    # still mutable
        return me
 
    def build(self):
        return User(self.name, self.age)  # immutable
 

Adapter

Adapter converts one interface into another so two incompatible modules can work together.

# existing interface we expect
class Notifier:
    def send(self, text):
        print("sending:", text)
 
# incompatible external library
class ExternalService:
    def push_message(self, msg):
        print("external:", msg)
 
# adapter converts one to the other
class ExternalServiceAdapter(Notifier):
    def __init__(self, service):
        self.service = service
 
    def send(self, text):
        self.service.push_message(text)
 
notifier = ExternalServiceAdapter(ExternalService())
notifier.send("hello")

Strategy

Strategy lets you swap algorithms/behaviors at runtime by depending on an abstraction.

class PaymentStrategy:
    def pay(self, amt):
        pass
 
class UPI(PaymentStrategy):
    def pay(self, amt):
        print("upi:", amt)
 
class Card(PaymentStrategy):
    def pay(self, amt):
        print("card:", amt)
 
class Checkout:
    def __init__(self, strategy: PaymentStrategy):
        self.strategy = strategy
 
    def process(self, amt):
        self.strategy.pay(amt)
 
Checkout(Card()).process(100)
Checkout(UPI()).process(200)
 

Decorater

Decorator adds extra behavior to an object without changing its original class.
And it works at runtime.

class Payment:
    def pay(self, amt):
        print("paid:", amt)
 
class LogDecorator(Payment):
    def __init__(self, payment):
        self.payment = payment
 
    def pay(self, amt):
        print("log: paying", amt)
        self.payment.pay(amt)
 
p = LogDecorator(Payment())
p.pay(100)