← Назад к вопросам

Что такое принцип единственной ответственности (SRP, Single Responsibility Principle) в ООП?

2.3 Middle🔥 221 комментариев
#Архитектура и паттерны

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Single Responsibility Principle (SRP)

Принцип единственной ответственности (SRP) — это первый принцип SOLID, который гласит: каждый класс должен иметь одну и только одну причину для изменения. Проще говоря, каждый класс должен отвечать за одно и только одно.

Зачем нужен SRP

Когда класс имеет множество ответственностей, он становится:

  1. Хрупким — изменения в одной функции влияют на другие
  2. Сложным для тестирования — нужно мокировать множество зависимостей
  3. Сложным для переиспользования — нельзя использовать отдельно
  4. Трудным для понимания — много логики в одном месте

Пример нарушения SRP

# ПЛОХО — класс имеет слишком много ответственностей
class User:
    def __init__(self, name, email):
        self.name = name
        self.email = email
    
    # Ответственность 1: управление данными пользователя
    def save_to_database(self):
        db.insert('users', {'name': self.name, 'email': self.email})
    
    # Ответственность 2: отправка email
    def send_welcome_email(self):
        smtp.send(
            to=self.email,
            subject="Welcome",
            body="Welcome to our platform!"
        )
    
    # Ответственность 3: логирование
    def log_user_action(self, action):
        logger.info(f"User {self.name} performed {action}")
    
    # Ответственность 4: валидация
    def validate_email(self):
        if '@' not in self.email:
            raise ValueError("Invalid email")

Проблемы:

  • Если изменится способ отправки email, нужно менять класс User
  • Если изменится структура логирования, опять User
  • Если изменится способ сохранения в БД, снова User
  • Очень сложно тестировать без реальной БД, SMTP и логгера

Рефакторинг по SRP

Разделяем ответственности на отдельные классы:

# Ответственность 1: данные пользователя
class User:
    def __init__(self, name: str, email: str):
        self.name = name
        self.email = email

# Ответственность 2: сохранение в БД
class UserRepository:
    def save(self, user: User) -> None:
        db.insert('users', {
            'name': user.name,
            'email': user.email
        })
    
    def find_by_email(self, email: str) -> User:
        row = db.select_one('users', {'email': email})
        return User(row['name'], row['email'])

# Ответственность 3: отправка email
class EmailService:
    def send_welcome_email(self, user: User) -> None:
        smtp.send(
            to=user.email,
            subject="Welcome",
            body="Welcome to our platform!"
        )
    
    def send_password_reset(self, user: User, token: str) -> None:
        smtp.send(
            to=user.email,
            subject="Reset Password",
            body=f"Click here to reset: {token}"
        )

# Ответственность 4: логирование
class UserLogger:
    def log_registration(self, user: User) -> None:
        logger.info(f"User registered: {user.email}")
    
    def log_login(self, user: User) -> None:
        logger.info(f"User logged in: {user.email}")

# Ответственность 5: валидация
class UserValidator:
    @staticmethod
    def validate_email(email: str) -> bool:
        return '@' in email and '.' in email.split('@')[1]
    
    @staticmethod
    def validate_name(name: str) -> bool:
        return len(name) >= 2

# Теперь использование:
def register_user(name: str, email: str) -> None:
    # Валидация
    if not UserValidator.validate_email(email):
        raise ValueError("Invalid email")
    
    # Создание
    user = User(name, email)
    
    # Сохранение
    repository = UserRepository()
    repository.save(user)
    
    # Email
    email_service = EmailService()
    email_service.send_welcome_email(user)
    
    # Логирование
    logger = UserLogger()
    logger.log_registration(user)

Преимущества:

  • Каждый класс отвечает за одно
  • Легко тестировать (можно мокировать зависимости)
  • Легко расширять (добавить новый способ отправки email)
  • Легко переиспользовать (EmailService можно использовать в других местах)

Практические примеры

Плохо: Класс с множественными ответственностями

class Report:
    def __init__(self, data: List[dict]):
        self.data = data
    
    # Ответственность 1: анализ данных
    def calculate_total(self) -> float:
        return sum(item['amount'] for item in self.data)
    
    # Ответственность 2: форматирование
    def format_as_html(self) -> str:
        html = "<table>"
        for item in self.data:
            html += f"<tr><td>{item['name']}</td></tr>"
        html += "</table>"
        return html
    
    # Ответственность 3: сохранение файла
    def save_to_file(self, filename: str) -> None:
        with open(filename, 'w') as f:
            f.write(self.format_as_html())
    
    # Ответственность 4: отправка по email
    def send_via_email(self, recipient: str) -> None:
        email_body = self.format_as_html()
        smtp.send(to=recipient, body=email_body)

Хорошо: Разделённые ответственности

# Анализ данных
class ReportData:
    def __init__(self, data: List[dict]):
        self.data = data
    
    def calculate_total(self) -> float:
        return sum(item['amount'] for item in self.data)
    
    def get_items(self) -> List[dict]:
        return self.data

# Форматирование
class ReportFormatter:
    def format_as_html(self, report_data: ReportData) -> str:
        html = "<table>"
        for item in report_data.get_items():
            html += f"<tr><td>{item['name']}</td></tr>"
        html += "</table>"
        return html
    
    def format_as_csv(self, report_data: ReportData) -> str:
        lines = []
        for item in report_data.get_items():
            lines.append(f"{item['name']},{item['amount']}")
        return "\n".join(lines)

# Сохранение файла
class ReportWriter:
    def save_to_file(self, filename: str, content: str) -> None:
        with open(filename, 'w') as f:
            f.write(content)

# Отправка по email
class ReportEmailer:
    def send(self, recipient: str, content: str, subject: str) -> None:
        smtp.send(to=recipient, subject=subject, body=content)

# Использование
report_data = ReportData(data)
formatter = ReportFormatter()
html_content = formatter.format_as_html(report_data)

writer = ReportWriter()
writer.save_to_file('report.html', html_content)

emailr = ReportEmailer()
emailr.send('user@example.com', html_content, 'Monthly Report')

Как определить нарушение SRP

Вопросы, которые помогут выявить нарушение:

  1. Есть ли в классе слово "и"? — "класс отвечает за подготовку данных И отправку email"
  2. Сколько причин изменить класс? — если больше одной, нарушаем SRP
  3. Можно ли переиспользовать отдельные части? — если нет, вероятно, слишком много ответственности
  4. Легко ли назвать класс? — трудно назвать → слишком много функций
# Трудно назвать класс — явный признак нарушения SRP
class UserManager:  # Manager обычно скрывает множество ответственностей
    pass

# Хорошие имена — одна ответственность
class UserRepository:
    pass

class PasswordHasher:
    pass

class EmailValidator:
    pass

Граница между SRP и избыточной сложностью

Нужно найти баланс. Не всё нужно разделять до мельчайших деталей:

# СЛИШКОМ сильно разделено — избыточно
class Name:
    def __init__(self, value: str):
        self.value = value

class FirstName(Name):
    pass

class LastName(Name):
    pass

class FullName:
    def __init__(self, first: FirstName, last: LastName):
        self.first = first
        self.last = last

# НОРМАЛЬНО — логичное разделение
class User:
    def __init__(self, name: str, email: str):
        self.name = name
        self.email = email

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

SRP в контексте слоёв архитектуры

# Domain layer (бизнес-логика)
class Order:
    def __init__(self, items: List[Item]):
        self.items = items
    
    def calculate_total(self) -> float:
        return sum(item.price for item in self.items)

# Application layer (use cases)
class CreateOrderUseCase:
    def execute(self, user_id: str, items: List[Item]) -> Order:
        order = Order(items)
        # Координирует разные сервисы
        return order

# Infrastructure layer (внешние зависимости)
class OrderRepository:
    def save(self, order: Order) -> None:
        pass

class OrderNotifier:
    def notify_user(self, order: Order, user_email: str) -> None:
        pass

Вывод

SRP — это не только о разделении кода на множество классов, это о чистоте архитектуры и удобстве разработки. Классы с одной ответственностью:

  • Легче тестировать
  • Легче понимать
  • Легче изменять
  • Легче переиспользовать

Применяй SRP разумно, но не впадай в крайность избыточного разделения.