Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Буква O в SOLID: Open/Closed Principle
Буква O в SOLID расшифровывается как Open/Closed Principle (OCP) — это один из самых важных и часто неправильно понимаемых принципов.
Определение
Open for Extension, Closed for Modification
Это означает:
- Open for Extension — ты можешь расширить функционал без проблем
- Closed for Modification — ты НЕ должен менять существующий код
Вместо того чтобы менять старый код, нужно добавлять новый код.
Проблемный пример
# ❌ НАРУШЕНИЕ OCP: нужно менять класс для каждого нового типа
class PaymentProcessor:
def process_payment(self, payment):
if payment.type == "credit_card":
# Обработка кредитной карты
charge_credit_card(payment)
elif payment.type == "paypal":
# Обработка PayPal
charge_paypal(payment)
elif payment.type == "bitcoin":
# Обработка Bitcoin
charge_bitcoin(payment)
# Завтра появляется Google Pay
# Нужно менять класс!
# elif payment.type == "google_pay":
# charge_google_pay(payment)
# Проблемы:
# 1. При добавлении нового метода платежа нужно менять PaymentProcessor
# 2. Риск сломать существующий функционал
# 3. Класс растёт бесконечно
# 4. Нарушается SRP (Single Responsibility)
# 5. Нарушается OCP
Правильное решение через полиморфизм
# ✅ СОБЛЮДЕНИЕ OCP: добавляем новые типы БЕЗ изменения старого кода
from abc import ABC, abstractmethod
# Базовый класс (ЗАКРЫТ для модификации)
class PaymentMethod(ABC):
@abstractmethod
def process(self, amount: float) -> bool:
"""Обработать платёж"""
pass
# Конкретные реализации (ОТКРЫТЫ для расширения)
class CreditCardPayment(PaymentMethod):
def __init__(self, card_number: str):
self.card_number = card_number
def process(self, amount: float) -> bool:
print(f"Charging credit card {self.card_number} for ${amount}")
# Логика обработки credit card
return True
class PayPalPayment(PaymentMethod):
def __init__(self, email: str):
self.email = email
def process(self, amount: float) -> bool:
print(f"Charging PayPal account {self.email} for ${amount}")
# Логика обработки PayPal
return True
class BitcoinPayment(PaymentMethod):
def __init__(self, wallet_address: str):
self.wallet_address = wallet_address
def process(self, amount: float) -> bool:
print(f"Charging Bitcoin wallet {self.wallet_address} for ${amount}")
# Логика обработки Bitcoin
return True
# Завтра появляется Google Pay — просто добавляем новый класс
class GooglePayPayment(PaymentMethod):
def __init__(self, account_id: str):
self.account_id = account_id
def process(self, amount: float) -> bool:
print(f"Charging Google Pay account {self.account_id} for ${amount}")
return True
# Процессор НЕ МЕНЯЕТСЯ! (CLOSED for modification)
class PaymentProcessor:
def process_payment(self, payment: PaymentMethod, amount: float) -> bool:
# Работает с ЛЮБЫМ PaymentMethod
return payment.process(amount)
# Использование
processor = PaymentProcessor()
# Сегодня используем credit card
card_payment = CreditCardPayment("1234-5678-9012-3456")
processor.process_payment(card_payment, 100.0)
# Завтра добавляем Google Pay
# Никакие изменения в PaymentProcessor не требуются!
google_payment = GooglePayPayment("user@gmail.com")
processor.process_payment(google_payment, 50.0)
Ключевые моменты
# 1. CLOSED for Modification
# PaymentProcessor не меняется при добавлении новых методов платежа
# 2. OPEN for Extension
# Мы расширяем функционал, создавая новые классы PaymentMethod
# 3. Абстракция
# Процессор работает с абстрактным интерфейсом, не с конкретными классами
# 4. Полиморфизм
# Благодаря наследованию разные типы платежей обрабатываются одинаково
Пример с Dependency Injection
# ✅ Ещё лучше: используем DI для максимальной гибкости
from typing import Protocol
class Logger(Protocol):
def log(self, message: str) -> None:
...
class ConsoleLogger:
def log(self, message: str) -> None:
print(f"[LOG] {message}")
class FileLogger:
def __init__(self, filename: str):
self.filename = filename
def log(self, message: str) -> None:
with open(self.filename, "a") as f:
f.write(f"[LOG] {message}\n")
class Application:
def __init__(self, logger: Logger):
self.logger = logger # Зависимость вводится извне
def run(self):
self.logger.log("Application started")
# ...
self.logger.log("Application finished")
# Использование
app_with_console = Application(ConsoleLogger())
app_with_console.run()
app_with_file = Application(FileLogger("app.log"))
app_with_file.run()
# Application класс ЗАКРЫТ для модификации
# Но ОТКРЫТ для расширения (новые логгеры)
Хорошее vs Плохое
# ❌ ПЛОХО: нарушает OCP
class ReportGenerator:
def generate(self, format: str):
if format == "pdf":
return self.generate_pdf()
elif format == "excel":
return self.generate_excel()
elif format == "csv":
return self.generate_csv()
# Каждый новый формат требует изменения класса!
# ✅ ХОРОШО: соблюдает OCP
class ReportFormatter(ABC):
@abstractmethod
def format(self, data: dict) -> bytes:
pass
class PdfFormatter(ReportFormatter):
def format(self, data: dict) -> bytes:
# Генерируем PDF
pass
class ExcelFormatter(ReportFormatter):
def format(self, data: dict) -> bytes:
# Генерируем Excel
pass
class CsvFormatter(ReportFormatter):
def format(self, data: dict) -> bytes:
# Генерируем CSV
pass
class ReportGenerator:
def __init__(self, formatter: ReportFormatter):
self.formatter = formatter
def generate(self, data: dict) -> bytes:
return self.formatter.format(data) # Один метод!
# Новый формат? Просто добавляем JsonFormatter
# ReportGenerator не меняется!
OCP в действии: Strategy Pattern
# ✅ Использование Strategy для обработки разных стратегий
from abc import ABC, abstractmethod
class PricingStrategy(ABC):
@abstractmethod
def calculate(self, base_price: float) -> float:
pass
class RegularPrice(PricingStrategy):
def calculate(self, base_price: float) -> float:
return base_price # Обычная цена
class VIPDiscount(PricingStrategy):
def __init__(self, discount_percent: float):
self.discount_percent = discount_percent
def calculate(self, base_price: float) -> float:
return base_price * (1 - self.discount_percent / 100)
class SeasonalSale(PricingStrategy):
def __init__(self, sale_percent: float):
self.sale_percent = sale_percent
def calculate(self, base_price: float) -> float:
return base_price * (1 - self.sale_percent / 100)
class Product:
def __init__(self, name: str, base_price: float, strategy: PricingStrategy):
self.name = name
self.base_price = base_price
self.strategy = strategy
def get_price(self) -> float:
return self.strategy.calculate(self.base_price)
# Использование
regular_product = Product("Book", 100, RegularPrice())
print(f"Regular price: ${regular_product.get_price()}") # $100
vip_product = Product("Book", 100, VIPDiscount(20))
print(f"VIP price: ${vip_product.get_price()}") # $80
sale_product = Product("Book", 100, SeasonalSale(30))
print(f"Sale price: ${sale_product.get_price()}") # $70
# Новая стратегия через 3 месяца?
class BulkDiscount(PricingStrategy):
def calculate(self, base_price: float) -> float:
return base_price * 0.85
bulk_product = Product("Book", 100, BulkDiscount())
print(f"Bulk price: ${bulk_product.get_price()}") # $85
# Product класс никогда не менялся!
OCP vs DRY vs YAGNI
# Баланс важен!
# ❌ Слишком усердно применяем OCP (Over-engineering)
class PaymentProcessor:
pass
class CreditCardPaymentProcessor(PaymentProcessor):
pass
class CreditCardVisa(CreditCardPaymentProcessor):
pass
class CreditCardVisaConsumer(CreditCardVisa):
pass
class CreditCardVisaConsumerUS(CreditCardVisaConsumer):
pass
# Это оверинжиниринг!
# ✅ Правильный баланс
class PaymentMethod(ABC):
@abstractmethod
def process(self, amount: float) -> bool:
pass
class CreditCardPayment(PaymentMethod):
def process(self, amount: float) -> bool:
# Обработка для всех кредитных карт
pass
class PayPalPayment(PaymentMethod):
def process(self, amount: float) -> bool:
# Обработка PayPal
pass
# Добавляем специфику только когда нужна
Когда НЕ применять OCP
# OCP имеет смысл когда:
# 1. Точно знаем, что будут новые расширения
# 2. Расширения будут частыми
# 3. Стоимость изменений высока
# Когда OCP переусложняет (YAGNI):
# 1. Проект маленький и не растёт
# 2. Только одна реализация и не будет других
# 3. Требования стабильны
Итоговое правило
# Open/Closed Principle говорит:
# "Проектируй код так, чтобы новые требования решались через РАСШИРЕНИЕ,
# а не через МОДИФИКАЦИЮ существующего кода."
# Это достигается через:
# 1. Абстракции (базовые классы, интерфейсы)
# 2. Полиморфизм (разные реализации одного интерфейса)
# 3. Composition (использование объектов)
# 4. Dependency Injection (зависимости извне)
# Результат:
# - Новые фичи = новые классы (не изменения старых)
# - Меньше багов (не трогаем старый код)
# - Легче тестировать (каждый класс отдельно)
# - Легче масштабировать
OCP — это основа гибкой и масштабируемой архитектуры!