## 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)