Когда паттерны уместны, когда over-engineering, антипаттерны
Паттерны — это инструменты, а не цели. Используйте их там, где они решают проблему, а не там, где можно добавить «красивый» паттерн.
# Strategy: разные алгоритмы скидок
class DiscountStrategy(ABC):
@abstractmethod
def apply(self, price: float) -> float:
pass
class NoDiscount(DiscountStrategy):
def apply(self, price: float) -> float:
return price
class PercentageDiscount(DiscountStrategy):
def __init__(self, percent: float):
self.percent = percent
def apply(self, price: float) -> float:
return price * (1 - self.percent)
# Использование зависит от контекста — паттерн оправдан
def calculate_price(price: float, strategy: DiscountStrategy) -> float:
return strategy.apply(price)# ❌ Избыточно: простой if/else лучше
class PriceCalculatorFactory:
@staticmethod
def create_calculator(type: str) -> PriceCalculator:
if type == "simple":
return SimplePriceCalculator()
elif type == "complex":
return ComplexPriceCalculator()
# 100 строк фабрик...
# ✅ Проще:
def calculate_price(price: float, discount: float = 0) -> float:
return price * (1 - discount)Правило: Паттерн оправдан, когда:
# ✅ Правильно: абстракция доступа к данным
class UserRepository(ABC):
@abstractmethod
def find_by_id(self, user_id: int) -> Optional[User]:
pass
@abstractmethod
def save(self, user: User) -> None:
pass
class SQLUserRepository(UserRepository):
def __init__(self, db):
self.db = db
def find_by_id(self, user_id: int) -> Optional[User]:
row = self.db.execute("SELECT * FROM users WHERE id = ?", user_id)
return User.from_row(row) if row else None
def save(self, user: User) -> None:
self.db.execute("INSERT OR REPLACE INTO users ...", user.to_dict())
# ✅ В тестах можно подменить
class FakeUserRepository(UserRepository):
def __init__(self):
self._users = {}
def find_by_id(self, user_id: int) -> Optional[User]:
return self._users.get(user_id)# ✅ Правильно: бизнес-логика в сервисе
class OrderService:
def __init__(self, order_repo: OrderRepository,
payment_gateway: PaymentGateway,
email_service: EmailService):
self.order_repo = order_repo
self.payment_gateway = payment_gateway
self.email_service = email_service
def place_order(self, user_id: int, items: list) -> Order:
# Бизнес-логика
order = Order.create(user_id, items)
total = order.calculate_total()
# Внешний сервис
payment = self.payment_gateway.charge(total)
if not payment.success:
raise PaymentFailedError()
# Сохранение
self.order_repo.save(order)
# Уведомление
self.email_service.send_order_confirmation(order)
return order# ✅ Оправданно: создание сложных объектов
class NotificationFactory:
@staticmethod
def create_notification(channel: str, config: dict) -> Notification:
if channel == "email":
return EmailNotification(config["smtp_host"])
elif channel == "sms":
return SMSNotification(config["twilio_key"])
elif channel == "push":
return PushNotification(config["firebase_key"])
raise ValueError(f"Unknown channel: {channel}")
# ❌ Избыточно: простой конструктор
class UserFactory:
@staticmethod
def create(name: str, email: str) -> User:
return User(name=name, email=email) # Зачем фабрика?# ❌ Антипаттерн: класс делает всё
class Application:
def __init__(self):
self.db = ...
self.cache = ...
self.logger = ...
self.email = ...
def process_order(self, ...): ...
def send_email(self, ...): ...
def log_action(self, ...): ...
def connect_db(self, ...): ...
# 2000 строк кода
# ✅ Разделить на классы
class OrderService, EmailService, Logger, Database# ❌ Анемичная модель: только данные
class Order:
def __init__(self):
self.id = None
self.user_id = None
self.items = []
self.total = None
# Геттеры/сеттеры, никакой логики
# ✅ Богатая модель: данные + логика
class Order:
def __init__(self, user_id: int):
self.id = generate_id()
self.user_id = user_id
self.items = []
self.status = "draft"
def add_item(self, product: Product, quantity: int):
if self.status != "draft":
raise OrderAlreadyPlacedError()
self.items.append(OrderItem(product, quantity))
def calculate_total(self) -> Decimal:
return sum(item.subtotal for item in self.items)
def place(self):
if not self.items:
raise EmptyOrderError()
self.status = "placed"# ❌ Преждевременная абстракция
class IDataProcessor(ABC):
@abstractmethod
def process(self, data: Any) -> Any:
pass
class XMLDataProcessor(IDataProcessor):
def process(self, data: Any) -> Any:
...
class JSONDataProcessor(IDataProcessor):
def process(self, data: Any) -> Any:
...
# В проекте только JSON, XML не планируется!
# ✅ Проще:
class JSONProcessor:
def process(self, data: dict) -> dict:
...Ключевая мысль: Лучший паттерн — тот, который не нужен. Используйте паттерны осознанно, когда они упрощают код, а не усложняют.
Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.