Фабричные методы через @classmethod, абстрактные фабрики для семейств объектов. Dependency Injection через фабрики.
Фабрики не создают объекты — они скрывают сложность выбора.
Factory Method определяет интерфейс для создания объекта, но позволяет подклассам или параметрам решать, какой именно класс instantiate.
# ❌ ПЛОХО: Клиент знает о конкретных классах
class PaymentProcessor:
def process(self, payment_type: str, amount: float):
if payment_type == "card":
payment = CardPayment(amount)
elif payment_type == "paypal":
payment = PayPalPayment(amount)
elif payment_type == "crypto":
payment = CryptoPayment(amount)
else:
raise ValueError(f"Unknown type: {payment_type}")
payment.pay()
# Проблема:
# 1. Нарушен Open-Closed Principle — добавление типа требует изменения кода
# 2. Клиент зависит от конкретных классов
# 3. Сложно тестировать — нельзя подменить создание объектов# ✅ ХОРОШО: Фабричный метод
from abc import ABC, abstractmethod
class Payment(ABC):
def __init__(self, amount: float):
self.amount = amount
@abstractmethod
def pay(self):
pass
class CardPayment(Payment):
def pay(self):
print(f"Processing card payment: ${self.amount}")
class PayPalPayment(Payment):
def pay(self):
print(f"Processing PayPal payment: ${self.amount}")
class CryptoPayment(Payment):
def pay(self):
print(f"Processing crypto payment: ${self.amount}")
class PaymentFactory:
@classmethod
def create(cls, payment_type: str, amount: float) -> Payment:
factories = {
"card": CardPayment,
"paypal": PayPalPayment,
"crypto": CryptoPayment,
}
if payment_type not in factories:
raise ValueError(f"Unknown type: {payment_type}")
return factories[payment_type](amount)
# Использование
payment = PaymentFactory.create("card", 100.0)
payment.pay() # Processing card payment: $100.0Преимущества:
Payment# ✅ Pythonic: Автоматическая регистрация подклассов
class Payment(ABC):
_registry = {}
def __init_subclass__(cls, payment_type: str = None, **kwargs):
super().__init_subclass__(**kwargs)
if payment_type:
cls._registry[payment_type] = cls
def __init__(self, amount: float):
self.amount = amount
@abstractmethod
def pay(self):
pass
@classmethod
def create(cls, payment_type: str, amount: float) -> "Payment":
if payment_type not in cls._registry:
raise ValueError(f"Unknown type: {payment_type}")
return cls._registry[payment_type](amount)
# Подклассы автоматически регистрируются
@Payment.register
class CardPayment(Payment, payment_type="card"):
def pay(self):
print(f"Card: ${self.amount}")
class PayPalPayment(Payment, payment_type="paypal"):
def pay(self):
print(f"PayPal: ${self.amount}")
# Использование
payment = Payment.create("paypal", 50.0)from typing import Protocol, Type
class Payment(Protocol):
amount: float
def pay(self) -> None: ...
class PaymentFactory(Protocol):
def create(self, amount: float) -> Payment: ...
class CardPayment:
def __init__(self, amount: float):
self.amount = amount
def pay(self):
print(f"Card: ${self.amount}")
class CardPaymentFactory:
def create(self, amount: float) -> CardPayment:
return CardPayment(amount)
# Dependency Injection через фабрику
class PaymentProcessor:
def __init__(self, factory: PaymentFactory):
self.factory = factory
def process(self, amount: float):
payment = self.factory.create(amount)
payment.pay()
# В production
processor = PaymentProcessor(CardPaymentFactory())
# В тестах
class MockPaymentFactory:
def create(self, amount: float):
class MockPayment:
def pay(self): pass
return MockPayment()
processor = PaymentProcessor(MockPaymentFactory())Abstract Factory предоставляет интерфейс для создания семейств связанных или зависимых объектов без привязки к конкретным классам.
# ❌ ПЛОХО: Клиент может смешать несовместимые объекты
class Button:
def render(self): pass
class Checkbox:
def render(self): pass
class WindowsButton(Button):
def render(self):
print("Render Windows button")
class MacButton(Button):
def render(self):
print("Render Mac button")
class WindowsCheckbox(Checkbox):
def render(self):
print("Render Windows checkbox")
class MacCheckbox(Checkbox):
def render(self):
print("Render Mac checkbox")
# Клиент может создать несовместимую смесь:
button = WindowsButton()
checkbox = MacCheckbox() # ❌ Разные OS!# ✅ ХОРОШО: Фабрика создаёт совместимые объекты
from abc import ABC, abstractmethod
class GUIFactory(ABC):
@abstractmethod
def create_button(self) -> Button:
pass
@abstractmethod
def create_checkbox(self) -> Checkbox:
pass
class WindowsFactory(GUIFactory):
def create_button(self) -> Button:
return WindowsButton()
def create_checkbox(self) -> Checkbox:
return WindowsCheckbox()
class MacFactory(GUIFactory):
def create_button(self) -> Button:
return MacButton()
def create_checkbox(self) -> Checkbox:
return MacCheckbox()
class Application:
def __init__(self, factory: GUIFactory):
self.button = factory.create_button()
self.checkbox = factory.create_checkbox()
def render(self):
self.button.render()
self.checkbox.render()
# Использование
app = Application(WindowsFactory()) # Или MacFactory()
app.render()Гарантия: Все созданные объекты принадлежат одному семейству.
from abc import ABC, abstractmethod
from typing import Protocol
import json
import yaml
class ConfigParser(ABC):
@abstractmethod
def parse(self, content: str) -> dict:
pass
class JSONParser(ConfigParser):
def parse(self, content: str) -> dict:
return json.loads(content)
class YAMLParser(ConfigParser):
def parse(self, content: str) -> dict:
return yaml.safe_load(content)
class ConfigParserFactory:
_parsers = {
".json": JSONParser,
".yaml": YAMLParser,
".yml": YAMLParser,
}
@classmethod
def get_parser(cls, filename: str) -> ConfigParser:
from pathlib import Path
ext = Path(filename).suffix.lower()
if ext not in cls._parsers:
raise ValueError(f"Unsupported format: {ext}")
return cls._parsers[ext]()
# Использование
parser = ConfigParserFactory.get_parser("config.yaml")
config = parser.parse("key: value")class DatabaseConnection:
def __init__(self, url: str):
self.url = url
self._connected = False
def connect(self):
print(f"Connecting to {self.url}")
self._connected = True
def query(self, sql: str):
if not self._connected:
raise RuntimeError("Not connected")
# ... реальный запрос ...
return []
class DatabaseFactory:
@staticmethod
def create(url: str) -> DatabaseConnection:
return DatabaseConnection(url)
class MockDatabaseConnection:
def __init__(self, url: str):
self.url = url
self._data = {"users": []}
def connect(self):
pass # No-op для тестов
def query(self, sql: str):
return self._data.get("users", [])
class MockDatabaseFactory:
@staticmethod
def create(url: str) -> MockDatabaseConnection:
return MockDatabaseConnection(url)
# В production
db = DatabaseFactory.create("postgresql://localhost")
# В тестах
db = MockDatabaseFactory.create("test://")from typing import Callable, TypeVar
T = TypeVar('T')
class Container:
"""Простой DI-контейнер через фабрики"""
def __init__(self):
self._services = {}
self._instances = {}
def register(self, name: str, factory: Callable[[], T], singleton: bool = True):
self._services[name] = (factory, singleton)
def get(self, name: str) -> T:
if name not in self._services:
raise KeyError(f"Service not found: {name}")
factory, singleton = self._services[name]
if singleton:
if name not in self._instances:
self._instances[name] = factory()
return self._instances[name]
return factory()
# Использование
container = Container()
container.register("logger", lambda: Logger())
container.register("database", lambda: DatabaseConnection("postgresql://..."))
container.register(
"user_service",
lambda: UserService(container.get("logger"), container.get("database")),
singleton=True
)
# Получение сервисов
user_service = container.get("user_service")# ❌ ПЛОХО: Factory без необходимости
class UserFactory:
@staticmethod
def create(name: str, email: str) -> User:
return User(name=name, email=email)
# Зачем это, если можно просто:
user = User(name="Alice", email="alice@example.com")Правило: Используйте Factory, когда:
| Аспект | Factory Method | Abstract Factory |
|---|---|---|
| Назначение | Создание одного объекта | Создание семейства объектов |
| Структура | Один метод | Несколько методов |
| Гибкость | Подклассы решают, что создавать | Фабрика определяет семейство |
| Сложность | Проще | Сложнее |
| Паттерн | Когда использовать | Pythonic-реализация |
|---|---|---|
| Factory Method | Выбор класса в runtime | @classmethod + словарь |
| Abstract Factory | Семейства связанных объектов | ABC + композиция |
| DI через Factory | Тестирование, конфигурация | Callable + контейнер |
Главный принцип: Фабрика скрывает что создаётся, клиент знает зачем создаётся.
Изучите тему Builder и Fluent Interface для пошагового создания сложных объектов.
Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.