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

Python Design Patterns Interview Q&A

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)
7 views13 pages

Python Design Patterns Interview Q&A

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

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

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 = [Link]()

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
[Link] = DatabaseConnection(connection_string)
self._initialized = True

def get_connection(self):
if not [Link].is_connected:
[Link]()
return [Link]

# 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):
[Link] = email

def update(self, stock_update: StockUpdate):


print(f"Email to {[Link]}: {stock_update.symbol} is now $
{stock_update.price}")

class SMSNotifier(StockObserver):
def __init__(self, phone: str):
[Link] = phone

def update(self, stock_update: StockUpdate):


print(f"SMS to {[Link]}: {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:
[Link](update)

# Usage
monitor = StockPriceMonitor()
email_notifier = EmailNotifier("user@[Link]")
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 = [Link]()
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 [Link](left) + middle + [Link](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 = [Link](data[:mid])
right = [Link](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]:
[Link](left[i])
i += 1
else:
[Link](right[j])
j += 1

[Link](left[i:])
[Link](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()}: {[Link](data)}")
sorter.set_strategy(QuickSortStrategy())
print(f"Using {sorter.get_strategy_name()}: {[Link](data)}")

sorter.set_strategy(MergeSortStrategy())
print(f"Using {sorter.get_strategy_name()}: {[Link](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)
[Link] = old
[Link] = new

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


return self._processor.process(text).replace([Link], [Link])

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: '{[Link](text)}'")

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

# Chain multiple decorators


processor = BasicTextProcessor()
processor = StripWhitespaceDecorator(processor)
processor = LowercaseDecorator(processor)
processor = ReplaceDecorator(processor, "hello", "hi")
processor = ReverseDecorator(processor)
print(f"Chained: '{[Link](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):
[Link] = ""
self.cursor_position = 0

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


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

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


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

def get_text(self) -> str:


return [Link]

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

def execute(self) -> None:


[Link].insert_text([Link], [Link])

def undo(self) -> None:


[Link].delete_text([Link], [Link] + len([Link]))

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

def execute(self) -> None:


self.deleted_text = [Link].delete_text([Link], [Link])

def undo(self) -> None:


[Link].insert_text(self.deleted_text, [Link])

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

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


[Link]()
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()
[Link]()
self.undo_stack.append(command)

def redo(self) -> None:


if self.undo_stack:
command = self.undo_stack.pop()
[Link]()
self.command_history.append(command)

def get_text(self) -> str:


return [Link].get_text()

# Usage
invoker = TextEditorInvoker()

# Execute commands
insert_cmd1 = InsertCommand([Link], "Hello", 0)
insert_cmd2 = InsertCommand([Link], " World", 5)
delete_cmd = DeleteCommand([Link], 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()}")

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

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

[Link]()
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 = [Link]("card_number")
expiry = [Link]("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": [Link]("currency", "USD"),
"payment_method": [Link]("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):
[Link] = processor

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


return [Link].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):
[Link] = []

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}


[Link](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 [Link] 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 = [Link]()

# 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):
[Link]: List[User] = []

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


[Link](user)

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


return next((user for user in [Link] if [Link] == 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 {[Link]}")

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

# Liskov Substitution Principle (LSP)


class UserService:
def __init__(self,
repository: UserRepository,
email_validator: EmailValidator,
password_validator: PasswordValidator,
notification_service: NotificationService):
[Link] = 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)


[Link](user)
self.notification_service.send_notification(user)

return user

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


return [Link].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@[Link]", "securepass123")
print(f"Created user: {user}")

# Find user
found_user = user_manager.find_user("john@[Link]")
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