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

Какие два принципа SOLID важнее остальных на твой взгляд?

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

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

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

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

Два самых важных принципа SOLID

Все пять принципов SOLID ценны, но некоторые имеют больший практический вес. На мой взгляд, Single Responsibility Principle (SRP) и Dependency Inversion Principle (DIP) — это фундамент хорошей архитектуры.

Почему SRP важнее остальных

Single Responsibility Principle: класс должен иметь одну причину для изменения

Это принцип номер один, потому что остальные из него вытекают:

# ПЛОХО: класс с множественной ответственностью
class UserManager:
    def create_user(self, email, password):
        """Создание пользователя"""
        # валидация
        if not is_valid_email(email):
            raise ValueError("Invalid email")
        
        # хеширование пароля
        hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt())
        
        # сохранение в БД
        db.execute("INSERT INTO users...")
        
        # отправка email
        send_email(email, "Welcome!")
        
        # логирование
        logger.info(f"User {email} created")
        
        # отправка уведомления
        notification_service.notify(email)
        
        # обновление кэша
        cache.delete("all_users")
    
    # Класс имеет 7 разных причин для изменения!
    # Новый способ отправки почты? Меняем класс.
    # Новый способ хеширования? Меняем класс.
    # Новый формат логирования? Меняем класс.

Правильный подход: разделяем ответственность

# Валидация
class EmailValidator:
    def validate(self, email: str) -> bool:
        return "@" in email and "." in email

# Хеширование
class PasswordHasher:
    def hash(self, password: str) -> str:
        return bcrypt.hashpw(password.encode(), bcrypt.gensalt())
    
    def verify(self, password: str, hashed: str) -> bool:
        return bcrypt.checkpw(password.encode(), hashed)

# Сохранение в БД
class UserRepository:
    def create(self, user: User) -> User:
        # только работа с БД
        return db.execute("INSERT INTO users...")

# Отправка уведомлений
class EmailService:
    def send_welcome(self, email: str):
        send_email(email, "Welcome!")

# Основной use case
class CreateUserUseCase:
    def __init__(self, 
                 validator: EmailValidator,
                 hasher: PasswordHasher,
                 repository: UserRepository,
                 email_service: EmailService):
        self.validator = validator
        self.hasher = hasher
        self.repository = repository
        self.email_service = email_service
    
    def execute(self, email: str, password: str) -> User:
        # Проверяем
        if not self.validator.validate(email):
            raise ValueError("Invalid email")
        
        # Хешируем
        hashed = self.hasher.hash(password)
        
        # Сохраняем
        user = User(email=email, password=hashed)
        user = self.repository.create(user)
        
        # Отправляем письмо
        self.email_service.send_welcome(email)
        
        return user

# Использование
use_case = CreateUserUseCase(
    validator=EmailValidator(),
    hasher=PasswordHasher(),
    repository=UserRepository(),
    email_service=EmailService()
)
user = use_case.execute("john@example.com", "secure_pass")

Почему это лучше:

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

Почему DIP почти так же важен

Dependency Inversion Principle: зависи от абстракций, не от конкретики

Без DIP код становится жёсткой связью сервисов:

# ПЛОХО: жёсткая зависимость
class PaymentService:
    def process(self, amount: float):
        # Жёсткая зависимость от Stripe
        stripe_client = StripeClient()
        charge = stripe_client.charge(amount)
        return charge

# Проблемы:
# 1. Нельзя переключиться на PayPal
# 2. Нельзя мокировать в тестах без реального Stripe
# 3. Тестирование медленное и дорогое

class TestPayment(TestCase):
    def test_process(self):
        # Реальный вызов Stripe!
        service = PaymentService()
        result = service.process(100)  # Опасно!

Правильный подход: зависимость от интерфейса

from abc import ABC, abstractmethod

# Интерфейс (абстракция)
class PaymentGateway(ABC):
    @abstractmethod
    def charge(self, amount: float) -> str:
        pass

# Реализации
class StripeGateway(PaymentGateway):
    def charge(self, amount: float) -> str:
        client = StripeClient()
        return client.charge(amount)

class PayPalGateway(PaymentGateway):
    def charge(self, amount: float) -> str:
        client = PayPalClient()
        return client.process(amount)

class MockGateway(PaymentGateway):
    def charge(self, amount: float) -> str:
        return "mock_charge_id"

# Сервис зависит от интерфейса
class PaymentService:
    def __init__(self, gateway: PaymentGateway):
        self.gateway = gateway
    
    def process(self, amount: float) -> str:
        return self.gateway.charge(amount)

# Использование
service_stripe = PaymentService(StripeGateway())
service_paypal = PaymentService(PayPalGateway())

# Тестирование
class TestPayment(TestCase):
    def test_process(self):
        service = PaymentService(MockGateway())
        result = service.process(100)  # Быстро, без побочных эффектов
        assert result == "mock_charge_id"

Почему это лучше:

  • Легко переключаться между реализациями
  • Тестирование без побочных эффектов
  • Новые реализации без изменения сервиса
  • Слабая связь (loose coupling)

Как они работают вместе

SRP и DIP дополняют друг друга:

# Пример: система уведомлений

# DIP: зависим от интерфейса
class NotificationChannel(ABC):
    @abstractmethod
    def send(self, message: str, recipient: str):
        pass

class EmailChannel(NotificationChannel):
    def send(self, message: str, recipient: str):
        email_service.send_email(recipient, message)

class SMSChannel(NotificationChannel):
    def send(self, message: str, recipient: str):
        sms_service.send_sms(recipient, message)

class TelegramChannel(NotificationChannel):
    def send(self, message: str, recipient: str):
        telegram_bot.send(recipient, message)

# SRP: каждый канал — одна ответственность
# DIP: NotificationService зависит от интерфейса

class NotificationService:
    def __init__(self, channels: List[NotificationChannel]):
        self.channels = channels
    
    def notify(self, message: str, recipient: str):
        for channel in self.channels:
            channel.send(message, recipient)

# Использование
services = [
    EmailChannel(),
    SMSChannel(),
    TelegramChannel()
]

notifier = NotificationService(services)
notifier.notify("Important!", "user@example.com")

Мой выбор и почему

Я выбираю SRP и DIP потому что:

  1. SRP — это основание. Если каждый класс делает одно, остальные принципы становятся проще
  2. DIP — это гибкость. Без DIP код становится спагетти, где всё зависит от всего
  3. Вместе они решают 80% архитектурных проблем — остальные три (LSP, ISP, OCP) вытекают из них

Остальные принципы:

  • OCP (Open/Closed) — вытекает из SRP + DIP
  • LSP (Liskov Substitution) — важна для типизации, но в Python менее критична
  • ISP (Interface Segregation) — следствие SRP (специализированные интерфейсы вместо монолитных)

Практически: если применяешь SRP и DIP, получишь чистую, тестируемую, масштабируемую архитектуру.

Какие два принципа SOLID важнее остальных на твой взгляд? | PrepBro