0% found this document useful (0 votes)
2 views13 pages

Python Design Patterns

The document outlines various design patterns and their implementations in Python, including Singleton, Factory, Observer, Strategy, Decorator, Command, and Adapter patterns. Each section provides a question related to a specific pattern, followed by a code solution demonstrating the implementation. The document serves as a resource for intermediate developers preparing for interviews on design patterns and SOLID principles.

Uploaded by

mukesh.khanijo
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views13 pages

Python Design Patterns

The document outlines various design patterns and their implementations in Python, including Singleton, Factory, Observer, Strategy, Decorator, Command, and Adapter patterns. Each section provides a question related to a specific pattern, followed by a code solution demonstrating the implementation. The document serves as a resource for intermediate developers preparing for interviews on design patterns and SOLID principles.

Uploaded by

mukesh.khanijo
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 13

================================================================================

PYTHON DESIGN PATTERNS & SOLID PRINCIPLES - INTERMEDIATE DEVELOPER INTERVIEW


QUESTIONS
================================================================================

Q1. SINGLETON PATTERN


---------------------
Question: Implement a thread-safe Singleton pattern for a database connection
manager.

Solution:
```python
import threading
from typing import Optional

class DatabaseConnection:
def __init__(self, connection_string: str):
self.connection_string = connection_string
self.is_connected = False

def connect(self):
self.is_connected = True
print(f"Connected to database: {self.connection_string}")

def disconnect(self):
self.is_connected = False
print("Disconnected from database")

class DatabaseManager:
_instance: Optional['DatabaseManager'] = None
_lock = threading.Lock()

def __new__(cls, connection_string: str = "default"):


if cls._instance is None:
with cls._lock:
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._initialized = False
return cls._instance

def __init__(self, connection_string: str = "default"):


if not self._initialized:
self.connection_string = connection_string
self.connection = DatabaseConnection(connection_string)
self._initialized = True

def get_connection(self):
if not self.connection.is_connected:
self.connection.connect()
return self.connection

# Usage
manager1 = DatabaseManager("postgresql://localhost:5432/mydb")
manager2 = DatabaseManager("mysql://localhost:3306/mydb")

print(manager1 is manager2) # True - same instance


print(manager1.connection_string) # postgresql://localhost:5432/mydb
```
Q2. FACTORY PATTERN
-------------------
Question: Create a factory pattern for different types of payment processors.

Solution:
```python
from abc import ABC, abstractmethod
from typing import Dict, Any

class PaymentProcessor(ABC):
@abstractmethod
def process_payment(self, amount: float, currency: str) -> bool:
pass

@abstractmethod
def get_processing_fee(self) -> float:
pass

class CreditCardProcessor(PaymentProcessor):
def process_payment(self, amount: float, currency: str) -> bool:
print(f"Processing credit card payment: {amount} {currency}")
return True

def get_processing_fee(self) -> float:


return 0.025 # 2.5%

class PayPalProcessor(PaymentProcessor):
def process_payment(self, amount: float, currency: str) -> bool:
print(f"Processing PayPal payment: {amount} {currency}")
return True

def get_processing_fee(self) -> float:


return 0.029 # 2.9%

class CryptoProcessor(PaymentProcessor):
def process_payment(self, amount: float, currency: str) -> bool:
print(f"Processing crypto payment: {amount} {currency}")
return True

def get_processing_fee(self) -> float:


return 0.01 # 1%

class PaymentProcessorFactory:
_processors = {
'credit_card': CreditCardProcessor,
'paypal': PayPalProcessor,
'crypto': CryptoProcessor
}

@classmethod
def create_processor(cls, processor_type: str) -> PaymentProcessor:
if processor_type not in cls._processors:
raise ValueError(f"Unknown processor type: {processor_type}")
return cls._processors[processor_type]()

@classmethod
def register_processor(cls, name: str, processor_class: type):
cls._processors[name] = processor_class
# Usage
factory = PaymentProcessorFactory()
credit_processor = factory.create_processor('credit_card')
paypal_processor = factory.create_processor('paypal')

credit_processor.process_payment(100.0, 'USD')
paypal_processor.process_payment(50.0, 'EUR')
```

Q3. OBSERVER PATTERN


--------------------
Question: Implement an observer pattern for a stock price monitoring system.

Solution:
```python
from abc import ABC, abstractmethod
from typing import List, Dict, Any
from dataclasses import dataclass

@dataclass
class StockUpdate:
symbol: str
price: float
change: float
timestamp: str

class StockObserver(ABC):
@abstractmethod
def update(self, stock_update: StockUpdate):
pass

class EmailNotifier(StockObserver):
def __init__(self, email: str):
self.email = email

def update(self, stock_update: StockUpdate):


print(f"Email to {self.email}: {stock_update.symbol} is now $
{stock_update.price}")

class SMSNotifier(StockObserver):
def __init__(self, phone: str):
self.phone = phone

def update(self, stock_update: StockUpdate):


print(f"SMS to {self.phone}: {stock_update.symbol} changed by
{stock_update.change}")

class StockPriceMonitor:
def __init__(self):
self._observers: List[StockObserver] = []
self._stock_prices: Dict[str, float] = {}

def add_observer(self, observer: StockObserver):


self._observers.append(observer)

def remove_observer(self, observer: StockObserver):


self._observers.remove(observer)

def update_stock_price(self, symbol: str, new_price: float, timestamp: str):


old_price = self._stock_prices.get(symbol, new_price)
change = new_price - old_price
self._stock_prices[symbol] = new_price

update = StockUpdate(symbol, new_price, change, timestamp)


self._notify_observers(update)

def _notify_observers(self, update: StockUpdate):


for observer in self._observers:
observer.update(update)

# Usage
monitor = StockPriceMonitor()
email_notifier = EmailNotifier("user@example.com")
sms_notifier = SMSNotifier("+1234567890")

monitor.add_observer(email_notifier)
monitor.add_observer(sms_notifier)

monitor.update_stock_price("AAPL", 150.25, "2024-01-15 10:30:00")


monitor.update_stock_price("GOOGL", 2800.50, "2024-01-15 10:31:00")
```

Q4. STRATEGY PATTERN


--------------------
Question: Implement a strategy pattern for different sorting algorithms.

Solution:
```python
from abc import ABC, abstractmethod
from typing import List, TypeVar, Generic

T = TypeVar('T')

class SortStrategy(ABC, Generic[T]):


@abstractmethod
def sort(self, data: List[T]) -> List[T]:
pass

@abstractmethod
def get_name(self) -> str:
pass

class BubbleSortStrategy(SortStrategy[T]):
def sort(self, data: List[T]) -> List[T]:
arr = data.copy()
n = len(arr)
for i in range(n):
for j in range(0, n - i - 1):
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
return arr

def get_name(self) -> str:


return "Bubble Sort"

class QuickSortStrategy(SortStrategy[T]):
def sort(self, data: List[T]) -> List[T]:
if len(data) <= 1:
return data
pivot = data[len(data) // 2]
left = [x for x in data if x < pivot]
middle = [x for x in data if x == pivot]
right = [x for x in data if x > pivot]
return self.sort(left) + middle + self.sort(right)

def get_name(self) -> str:


return "Quick Sort"

class MergeSortStrategy(SortStrategy[T]):
def sort(self, data: List[T]) -> List[T]:
if len(data) <= 1:
return data

mid = len(data) // 2
left = self.sort(data[:mid])
right = self.sort(data[mid:])

return self._merge(left, right)

def _merge(self, left: List[T], right: List[T]) -> List[T]:


result = []
i = j = 0

while i < len(left) and j < len(right):


if left[i] <= right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1

result.extend(left[i:])
result.extend(right[j:])
return result

def get_name(self) -> str:


return "Merge Sort"

class Sorter:
def __init__(self, strategy: SortStrategy[T]):
self._strategy = strategy

def set_strategy(self, strategy: SortStrategy[T]):


self._strategy = strategy

def sort(self, data: List[T]) -> List[T]:


return self._strategy.sort(data)

def get_strategy_name(self) -> str:


return self._strategy.get_name()

# Usage
data = [64, 34, 25, 12, 22, 11, 90]

sorter = Sorter(BubbleSortStrategy())
print(f"Using {sorter.get_strategy_name()}: {sorter.sort(data)}")
sorter.set_strategy(QuickSortStrategy())
print(f"Using {sorter.get_strategy_name()}: {sorter.sort(data)}")

sorter.set_strategy(MergeSortStrategy())
print(f"Using {sorter.get_strategy_name()}: {sorter.sort(data)}")
```

Q5. DECORATOR PATTERN


---------------------
Question: Implement a decorator pattern for adding functionality to text
processing.

Solution:
```python
from abc import ABC, abstractmethod
from typing import List

class TextProcessor(ABC):
@abstractmethod
def process(self, text: str) -> str:
pass

class BasicTextProcessor(TextProcessor):
def process(self, text: str) -> str:
return text

class TextProcessorDecorator(TextProcessor):
def __init__(self, processor: TextProcessor):
self._processor = processor

def process(self, text: str) -> str:


return self._processor.process(text)

class UppercaseDecorator(TextProcessorDecorator):
def process(self, text: str) -> str:
return self._processor.process(text).upper()

class LowercaseDecorator(TextProcessorDecorator):
def process(self, text: str) -> str:
return self._processor.process(text).lower()

class StripWhitespaceDecorator(TextProcessorDecorator):
def process(self, text: str) -> str:
return self._processor.process(text).strip()

class ReplaceDecorator(TextProcessorDecorator):
def __init__(self, processor: TextProcessor, old: str, new: str):
super().__init__(processor)
self.old = old
self.new = new

def process(self, text: str) -> str:


return self._processor.process(text).replace(self.old, self.new)

class ReverseDecorator(TextProcessorDecorator):
def process(self, text: str) -> str:
return self._processor.process(text)[::-1]

# Usage
text = " Hello, World! "

# Basic processing
processor = BasicTextProcessor()
print(f"Basic: '{processor.process(text)}'")

# Add decorators
processor = StripWhitespaceDecorator(processor)
processor = UppercaseDecorator(processor)
processor = ReplaceDecorator(processor, "WORLD", "PYTHON")
print(f"Decorated: '{processor.process(text)}'")

# Chain multiple decorators


processor = BasicTextProcessor()
processor = StripWhitespaceDecorator(processor)
processor = LowercaseDecorator(processor)
processor = ReplaceDecorator(processor, "hello", "hi")
processor = ReverseDecorator(processor)
print(f"Chained: '{processor.process(text)}'")
```

Q6. COMMAND PATTERN


-------------------
Question: Implement a command pattern for a text editor with undo/redo
functionality.

Solution:
```python
from abc import ABC, abstractmethod
from typing import List, Optional

class Command(ABC):
@abstractmethod
def execute(self) -> None:
pass

@abstractmethod
def undo(self) -> None:
pass

class TextEditor:
def __init__(self):
self.text = ""
self.cursor_position = 0

def insert_text(self, text: str, position: int) -> None:


self.text = self.text[:position] + text + self.text[position:]
self.cursor_position = position + len(text)

def delete_text(self, start: int, end: int) -> str:


deleted_text = self.text[start:end]
self.text = self.text[:start] + self.text[end:]
self.cursor_position = start
return deleted_text

def get_text(self) -> str:


return self.text

class InsertCommand(Command):
def __init__(self, editor: TextEditor, text: str, position: int):
self.editor = editor
self.text = text
self.position = position

def execute(self) -> None:


self.editor.insert_text(self.text, self.position)

def undo(self) -> None:


self.editor.delete_text(self.position, self.position + len(self.text))

class DeleteCommand(Command):
def __init__(self, editor: TextEditor, start: int, end: int):
self.editor = editor
self.start = start
self.end = end
self.deleted_text = ""

def execute(self) -> None:


self.deleted_text = self.editor.delete_text(self.start, self.end)

def undo(self) -> None:


self.editor.insert_text(self.deleted_text, self.start)

class TextEditorInvoker:
def __init__(self):
self.editor = TextEditor()
self.command_history: List[Command] = []
self.undo_stack: List[Command] = []

def execute_command(self, command: Command) -> None:


command.execute()
self.command_history.append(command)
self.undo_stack.clear() # Clear redo stack

def undo(self) -> None:


if self.command_history:
command = self.command_history.pop()
command.undo()
self.undo_stack.append(command)

def redo(self) -> None:


if self.undo_stack:
command = self.undo_stack.pop()
command.execute()
self.command_history.append(command)

def get_text(self) -> str:


return self.editor.get_text()

# Usage
invoker = TextEditorInvoker()

# Execute commands
insert_cmd1 = InsertCommand(invoker.editor, "Hello", 0)
insert_cmd2 = InsertCommand(invoker.editor, " World", 5)
delete_cmd = DeleteCommand(invoker.editor, 0, 5)

invoker.execute_command(insert_cmd1)
print(f"After insert 'Hello': {invoker.get_text()}")

invoker.execute_command(insert_cmd2)
print(f"After insert ' World': {invoker.get_text()}")

invoker.execute_command(delete_cmd)
print(f"After delete: {invoker.get_text()}")

invoker.undo()
print(f"After undo: {invoker.get_text()}")

invoker.undo()
print(f"After second undo: {invoker.get_text()}")

invoker.redo()
print(f"After redo: {invoker.get_text()}")
```

Q7. ADAPTER PATTERN


-------------------
Question: Create an adapter pattern to make incompatible payment systems work
together.

Solution:
```python
from abc import ABC, abstractmethod
from typing import Dict, Any

# Legacy payment system


class LegacyPaymentSystem:
def make_payment(self, amount: float, card_number: str, expiry: str) ->
Dict[str, Any]:
return {
"status": "success",
"transaction_id": f"LEG_{hash(card_number)}",
"amount": amount,
"message": "Payment processed by legacy system"
}

# Modern payment system


class ModernPaymentSystem:
def process_payment(self, payment_data: Dict[str, Any]) -> Dict[str, Any]:
return {
"status": "success",
"transaction_id": f"MOD_{hash(str(payment_data))}",
"amount": payment_data.get("amount", 0),
"message": "Payment processed by modern system"
}

# Target interface
class PaymentProcessor(ABC):
@abstractmethod
def process_payment(self, amount: float, **kwargs) -> Dict[str, Any]:
pass

# Adapter for legacy system


class LegacyPaymentAdapter(PaymentProcessor):
def __init__(self, legacy_system: LegacyPaymentSystem):
self.legacy_system = legacy_system
def process_payment(self, amount: float, **kwargs) -> Dict[str, Any]:
card_number = kwargs.get("card_number")
expiry = kwargs.get("expiry")

if not card_number or not expiry:


raise ValueError("Legacy system requires card_number and expiry")

return self.legacy_system.make_payment(amount, card_number, expiry)

# Adapter for modern system


class ModernPaymentAdapter(PaymentProcessor):
def __init__(self, modern_system: ModernPaymentSystem):
self.modern_system = modern_system

def process_payment(self, amount: float, **kwargs) -> Dict[str, Any]:


payment_data = {
"amount": amount,
"currency": kwargs.get("currency", "USD"),
"payment_method": kwargs.get("payment_method", "card"),
"metadata": kwargs
}

return self.modern_system.process_payment(payment_data)

# Payment service that works with any adapter


class PaymentService:
def __init__(self, processor: PaymentProcessor):
self.processor = processor

def pay(self, amount: float, **kwargs) -> Dict[str, Any]:


return self.processor.process_payment(amount, **kwargs)

# Usage
legacy_system = LegacyPaymentSystem()
modern_system = ModernPaymentSystem()

legacy_adapter = LegacyPaymentAdapter(legacy_system)
modern_adapter = ModernPaymentAdapter(modern_system)

# Use legacy system through adapter


legacy_service = PaymentService(legacy_adapter)
result1 = legacy_service.pay(100.0, card_number="1234-5678-9012-3456",
expiry="12/25")
print(f"Legacy payment: {result1}")

# Use modern system through adapter


modern_service = PaymentService(modern_adapter)
result2 = modern_service.pay(200.0, currency="EUR", payment_method="paypal")
print(f"Modern payment: {result2}")
```

Q8. SOLID PRINCIPLES IMPLEMENTATION


-----------------------------------
Question: Refactor a poorly designed class to follow SOLID principles.

Solution:
```python
from abc import ABC, abstractmethod
from typing import List, Dict, Any
from dataclasses import dataclass
from datetime import datetime

# Before (violating SOLID principles)


class BadUserManager:
def __init__(self):
self.users = []

def add_user(self, name: str, email: str, password: str):


# Violates SRP - handles validation, storage, and notification
if not self._validate_email(email):
raise ValueError("Invalid email")

user = {"name": name, "email": email, "password": password}


self.users.append(user)

# Violates OCP - hardcoded notification


self._send_email_notification(email)

def _validate_email(self, email: str) -> bool:


return "@" in email

def _send_email_notification(self, email: str):


print(f"Welcome email sent to {email}")

def get_user_by_email(self, email: str):


return next((user for user in self.users if user["email"] == email), None)

# After (following SOLID principles)

@dataclass
class User:
name: str
email: str
password: str
created_at: datetime = None

def __post_init__(self):
if self.created_at is None:
self.created_at = datetime.now()

# Single Responsibility Principle (SRP)


class EmailValidator:
def validate(self, email: str) -> bool:
return "@" in email and "." in email

class PasswordValidator:
def validate(self, password: str) -> bool:
return len(password) >= 8

class UserRepository:
def __init__(self):
self.users: List[User] = []

def save(self, user: User) -> None:


self.users.append(user)

def find_by_email(self, email: str) -> User:


return next((user for user in self.users if user.email == email), None)

# Open/Closed Principle (OCP)


class NotificationService(ABC):
@abstractmethod
def send_notification(self, user: User) -> None:
pass

class EmailNotificationService(NotificationService):
def send_notification(self, user: User) -> None:
print(f"Welcome email sent to {user.email}")

class SMSNotificationService(NotificationService):
def send_notification(self, user: User) -> None:
print(f"Welcome SMS sent to {user.name}")

# Liskov Substitution Principle (LSP)


class UserService:
def __init__(self,
repository: UserRepository,
email_validator: EmailValidator,
password_validator: PasswordValidator,
notification_service: NotificationService):
self.repository = repository
self.email_validator = email_validator
self.password_validator = password_validator
self.notification_service = notification_service

def create_user(self, name: str, email: str, password: str) -> User:
if not self.email_validator.validate(email):
raise ValueError("Invalid email")

if not self.password_validator.validate(password):
raise ValueError("Password too short")

user = User(name=name, email=email, password=password)


self.repository.save(user)
self.notification_service.send_notification(user)

return user

def get_user(self, email: str) -> User:


return self.repository.find_by_email(email)

# Interface Segregation Principle (ISP)


class ReadableRepository(ABC):
@abstractmethod
def find_by_email(self, email: str) -> User:
pass

class WritableRepository(ABC):
@abstractmethod
def save(self, user: User) -> None:
pass

class FullRepository(ReadableRepository, WritableRepository):


pass

# Dependency Inversion Principle (DIP)


class UserManager:
def __init__(self, user_service: UserService):
self.user_service = user_service

def register_user(self, name: str, email: str, password: str) -> User:
return self.user_service.create_user(name, email, password)

def find_user(self, email: str) -> User:


return self.user_service.get_user(email)

# Usage
repository = UserRepository()
email_validator = EmailValidator()
password_validator = PasswordValidator()
notification_service = EmailNotificationService()

user_service = UserService(repository, email_validator, password_validator,


notification_service)
user_manager = UserManager(user_service)

# Create user
user = user_manager.register_user("John Doe", "john@example.com", "securepass123")
print(f"Created user: {user}")

# Find user
found_user = user_manager.find_user("john@example.com")
print(f"Found user: {found_user}")
```

KEY SOLID PRINCIPLES:


=====================

1. **Single Responsibility Principle (SRP)**: A class should have only one reason
to change
2. **Open/Closed Principle (OCP)**: Open for extension, closed for modification
3. **Liskov Substitution Principle (LSP)**: Subtypes must be substitutable for
their base types
4. **Interface Segregation Principle (ISP)**: Clients shouldn't depend on
interfaces they don't use
5. **Dependency Inversion Principle (DIP)**: Depend on abstractions, not
concretions

BEST PRACTICES:
===============

1. **Use Composition over Inheritance**: Favor object composition over class


inheritance
2. **Program to Interfaces**: Code to interfaces, not implementations
3. **Keep Classes Small**: Single responsibility and focused functionality
4. **Use Dependency Injection**: Inject dependencies rather than creating them
5. **Follow Naming Conventions**: Clear, descriptive names for classes and methods
6. **Write Tests**: Design patterns should be testable

You might also like