SOLID

Как писать код, который не сложно поддерживать

S: Single Responsibility

Один класс — одна причина для изменения

❌ Нарушение

class User:
    def save(self): ...
    def send_email(self): ...
    def log_action(self): ...

Три причины изменить: БД, почта, логи

✅ Исправлено

class User:
    def __init__(self, repo, mailer, logger):
        self.repo = repo
        self.mailer = mailer
        self.logger = logger

    def save(self):
        self.repo.save(self)
        self.logger.log("User saved")

    def notify(self):
        self.mailer.send(self.email)

Класс не делает всё сам — делегирует

O: Open/Closed

Расширять — можно. Менять — нет.

❌ Нарушение

def render_chart(data, type):
    if type == 'bar':
        draw_bar(data)
    elif type == 'pie':
        draw_pie(data)
    # Как добавить 'line'? Придётся менять!

✅ Исправлено

from abc import ABC, abstractmethod

class Chart(ABC):
    @abstractmethod
    def render(self, data): pass

class BarChart(Chart): ...
class PieChart(Chart): ...
class LineChart(Chart): ...  # легко добавить!

def render(chart: Chart, data):
    chart.render(data)

Новый тип? Новая реализация — без правки старого кода

L: Liskov Substitution

Подкласс должен заменять родительский класс

❌ Нарушение

class Bird:
    def fly(self): ...

class Penguin(Bird):
    def fly(self):
        raise Exception("Я не летаю!")

fly() ломает ожидания

✅ Исправлено

class FlyingBird(ABC):
    @abstractmethod
    def fly(self): pass

class Sparrow(FlyingBird): ...

class Bird:
    pass

class Penguin(Bird):  # просто птица, не летающая
    pass

Наследование по поведению, а не по имени

I: Interface Segregation

Не заставляйте клиентов зависеть от того, что им не нужно

❌ Нарушение

class Worker(ABC):
    @abstractmethod
    def work(self): pass
    @abstractmethod
    def eat(self): pass  # А робот должен есть?

class Robot(Worker):
    def work(self): ...
    def eat(self): raise NotImplementedError

✅ Исправлено

class Workable(ABC):
    @abstractmethod
    def work(self): pass

class Eatable(ABC):
    @abstractmethod
    def eat(self): pass

class Human(Workable, Eatable): ...
class Robot(Workable):  # только работает

Каждый интерфейс — свой

D: Dependency Inversion

Зависимость от абстракций, а не от деталей

❌ Нарушение

class Notifier:
    def __init__(self):
        self.email = EmailService()  # жёсткая зависимость

    def notify(self, msg):
        self.email.send(msg)

Что если нужно SMS?

✅ Исправлено

class MessageService(ABC):
    @abstractmethod
    def send(self, msg): pass

class Notifier:
    def __init__(self, service: MessageService):
        self.service = service

    def notify(self, msg):
        self.service.send(msg)

# Можно передать EmailService, SMSService, SlackService...

Гибко, тестируемо, масштабируемо

🎯 Итог: SOLID

  • Single Responsibility
  • Open/Closed
  • Liskov Substitution
  • Interface Segregation
  • Dependency Inversion

Почему это важно?

Чтобы ваш код:

  • можно было менять без страха
  • было легко тестировать
  • не приходилось переписывать каждый раз
  • понимали другие инженеры
“Принципы SOLID — это не правила. Это щит от технического долга.”