Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Практический пример Single Responsibility Principle
SRP гласит: класс должен иметь одну причину для изменения. Давайте разберёмся на реальном примере.
Пример 1: Обработка заказов (антипаттерн)
# Плохо: класс делает ВСЁ
class Order:
def __init__(self, items, customer):
self.items = items
self.customer = customer
self.status = "pending"
def calculate_total(self):
"""Расчёт суммы"""
return sum(item.price for item in self.items)
def apply_discount(self, percent):
"""Применение скидки"""
total = self.calculate_total()
return total * (1 - percent / 100)
def send_confirmation_email(self):
"""Отправка письма"""
import smtplib
email = smtplib.SMTP("smtp.example.com")
email.send_message(f"Order confirmed for {self.customer.email}")
def save_to_database(self):
"""Сохранение в БД"""
import sqlite3
conn = sqlite3.connect("orders.db")
cursor = conn.cursor()
cursor.execute(f"INSERT INTO orders VALUES (...)")
conn.commit()
def generate_invoice_pdf(self):
"""Генерация счёта"""
from reportlab.pdfgen import canvas
c = canvas.Canvas("invoice.pdf")
# логика генерации
c.save()
Проблемы:
- Класс отвечает за слишком многое
- Если изменить формат письма → меняем Order
- Если изменить БД → меняем Order
- Если изменить генерацию PDF → меняем Order
- Тестировать сложно (нужны mock для всего)
- Переиспользование затруднено
Решение: разбить на специализированные классы
# Хорошо: каждый класс отвечает за одно
# 1. Класс для работы с заказом (только бизнес-логика)
class Order:
def __init__(self, items, customer):
self.items = items
self.customer = customer
self.status = "pending"
self.discount_percent = 0
def calculate_total(self):
"""ТОЛЬКО расчёт"""
total = sum(item.price for item in self.items)
return total * (1 - self.discount_percent / 100)
def apply_discount(self, percent):
"""ТОЛЬКО применение скидки"""
self.discount_percent = percent
# 2. Отдельный класс для отправки уведомлений
class OrderNotifier:
"""Отвечает ТОЛЬКО за уведомления"""
def __init__(self, email_service):
self.email_service = email_service
def notify_order_confirmed(self, order):
"""Отправка письма подтверждения"""
self.email_service.send(
to=order.customer.email,
subject="Заказ подтвержден",
body=f"Ваш заказ на сумму {order.calculate_total()} рублей подтвержден"
)
# 3. Отдельный класс для работы с БД
class OrderRepository:
"""Отвечает ТОЛЬКО за сохранение/загрузку"""
def __init__(self, database):
self.database = database
def save(self, order):
"""Сохранить заказ"""
self.database.execute(
"INSERT INTO orders (customer_id, total) VALUES (?, ?)",
(order.customer.id, order.calculate_total())
)
# 4. Отдельный класс для генерации отчётов
class InvoiceGenerator:
"""Отвечает ТОЛЬКО за генерацию счётов"""
def generate_pdf(self, order):
"""Создать PDF счёт"""
from reportlab.pdfgen import canvas
c = canvas.Canvas("invoice.pdf")
c.drawString(100, 100, f"Invoice for {order.customer.name}")
c.drawString(100, 80, f"Total: {order.calculate_total()}")
c.save()
return "invoice.pdf"
# 5. Главный орхестратор (бизнес-процесс)
class OrderProcessor:
"""Координирует процесс обработки заказа"""
def __init__(self, repository, notifier, invoice_generator):
self.repository = repository
self.notifier = notifier
self.invoice_generator = invoice_generator
def process_order(self, order):
"""Обработать заказ (оркестрирует всё)"""
# 1. Сохраняем
self.repository.save(order)
# 2. Уведомляем
self.notifier.notify_order_confirmed(order)
# 3. Генерируем счёт
invoice_path = self.invoice_generator.generate_pdf(order)
print(f"Заказ обработан. Счёт: {invoice_path}")
Использование:
# Инициализация
from services.email import EmailService
from db import Database
email_service = EmailService()
database = Database()
repository = OrderRepository(database)
notifier = OrderNotifier(email_service)
invoice_gen = InvoiceGenerator()
processor = OrderProcessor(repository, notifier, invoice_gen)
# Создаём и обрабатываем заказ
items = [Item("laptop", 1000), Item("mouse", 50)]
customer = Customer("john@example.com")
order = Order(items, customer)
order.apply_discount(10)
processor.process_order(order)
Преимущества этого подхода
-
Легко менять без побочных эффектов:
- Хочу изменить способ отправки писем? → Меняю EmailService
- Хочу в NoSQL вместо SQL? → Меняю OrderRepository
- Хочу JSON вместо PDF? → Меняю InvoiceGenerator
-
Тестирование упрощается:
# Тестируем только расчёт
def test_order_calculation():
items = [Item("item", 100)]
order = Order(items, Customer())
assert order.calculate_total() == 100
# Никакие mocks не нужны!
# Тестируем уведомление отдельно
def test_notification(mock_email_service):
notifier = OrderNotifier(mock_email_service)
order = Order([], Customer("test@example.com"))
notifier.notify_order_confirmed(order)
mock_email_service.send.assert_called_once()
- Переиспользование:
# Хочу использовать Order в другом месте без всех зависимостей?
# Просто использую Order класс
order = Order(items, customer)
print(f"Сумма заказа: {order.calculate_total()}")
# Всё просто!
- Добавление новых функций не ломает старый код:
# Хочу добавить SMS уведомление?
class SMSNotifier:
def notify_order_confirmed(self, order):
# Отправляем SMS
pass
# OrderProcessor не нужно менять!
processor = OrderProcessor(
repository,
[OrderNotifier(email), SMSNotifier()], # Просто добавляем
invoice_gen
)
Как я это определяю на практике
Если отвечу на вопрос: "Сколько причин у класса измениться?" — это SRP.
# Плохо
class Order:
# Причина 1: изменится бизнес-логика расчётов
def calculate_total(self):
pass
# Причина 2: изменится способ отправки писем
def send_email(self):
pass
# Причина 3: изменится БД
def save(self):
pass
# ТРИ ПРИЧИНЫ ДЛЯ ИЗМЕНЕНИЯ!
# Хорошо
class Order:
# ОДНА причина: изменится бизнес-логика
def calculate_total(self):
pass
# Другие причины в других классах
class OrderService:
def notify(self, order): pass # Причина: изменится уведомление
class OrderRepository:
def save(self, order): pass # Причина: изменится БД
Практический совет
Не переусложняй! SRP не значит, что каждый метод — отдельный класс.
# Может быть в одном классе
class OrderService:
def __init__(self, repository, notifier):
self.repository = repository
self.notifier = notifier
def create_and_process(self, items, customer):
order = Order(items, customer)
self.repository.save(order)
self.notifier.notify(order)
return order
# Это ОК, потому что класс отвечает за ОДИН процесс: создание и обработка заказа
# Меняется он только если меняется сам процесс
Итог
SRP = One Reason to Change
- Разделяй ответственность
- Каждый класс = одна ответственность
- Легче тестировать
- Легче переиспользовать
- Легче менять
- Код становится понятнее и поддерживаемее