Встречается на 36% собеседований по Python

Что такое SOLID

SOLID — это акроним пяти принципов объектно-ориентированного проектирования, сформулированных Робертом Мартином. Эти принципы помогают создавать гибкие, поддерживаемые и масштабируемые системы, снижая связанность и повышая устойчивость к изменениям. Каждый принцип решает конкретную проблему проектирования, а их совместное применение позволяет строить архитектуру, которую легко расширять и тестировать.

Как расшифровывается SOLID

  • S — Single Responsibility (Принцип единственной ответственности)
    Каждый класс должен отвечать только за одну операцию или иметь одну причину для изменения. Это упрощает понимание кода, тестирование и снижает риск побочных эффектов при модификациях.

  • O — Open-Closed (Принцип открытости-закрытости)
    Классы должны быть открыты для расширения, но закрыты для модификации. То есть новое поведение добавляется через наследование или композицию, а не изменением существующего кода.

  • L — Liskov Substitution (Принцип подстановки Барбары Лисков)
    Если класс S является подтипом T, то объекты типа T могут быть заменены объектами типа S без нарушения работы программы. Это гарантирует корректность наследования и полиморфизма.

  • I — Interface Segregation (Принцип разделения интерфейсов)
    Клиент не должен зависеть от методов, которые он не использует. Лучше иметь несколько узких интерфейсов, чем один широкий.

  • D — Dependency Inversion (Принцип инверсии зависимостей)
    Модули верхнего уровня не должны зависеть от модулей нижнего уровня; оба должны зависеть от абстракций. Абстракции не должны зависеть от деталей; детали должны зависеть от абстракций.

Примеры на Python

S — нарушение и исправление

python
# Нарушение: класс делает две вещи — хранит данные и сохраняет их в БД
class User:
    def __init__(self, name):
        self.name = name

    def save_to_db(self):
        # логика сохранения
        pass

# Исправление: разделяем ответственность
class User:
    def __init__(self, name):
        self.name = name

class UserRepository:
    def save(self, user):
        # логика сохранения
        pass

O — расширение без модификации

python
class Shape:
    def area(self):
        raise NotImplementedError

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

# Добавляем новую фигуру, не трогая существующий код
class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14 * self.radius ** 2

L — подстановка

python
class Bird:
    def fly(self):
        return "Летит"

class Penguin(Bird):
    def fly(self):
        raise Exception("Пингвин не летает")

# Нарушение: Penguin не может заменить Bird без ошибок
# Исправление: выделить интерфейс FlyingBird

I — разделение интерфейсов

python
# Нарушение: широкий интерфейс
class Worker:
    def work(self):
        pass
    def eat(self):
        pass

# Исправление: узкие интерфейсы
class Workable:
    def work(self):
        pass

class Eatable:
    def eat(self):
        pass

D — инверсия зависимостей

python
# Нарушение: высокоуровневый модуль зависит от конкретной реализации
class EmailSender:
    def send(self, message):
        pass

class NotificationService:
    def __init__(self):
        self.sender = EmailSender()  # жёсткая связь

# Исправление: зависимость от абстракции
class MessageSender:
    def send(self, message):
        pass

class EmailSender(MessageSender):
    def send(self, message):
        pass

class NotificationService:
    def __init__(self, sender: MessageSender):
        self.sender = sender  # внедрение зависимости

Подводные камни и частые ошибки

  • Слепое следование принципам: иногда излишнее разбиение на классы усложняет код. Применяйте принципы осознанно, учитывая контекст.
  • Нарушение LSP: часто встречается, когда наследник изменяет поведение базового класса неожиданным образом (например, выбрасывает исключения).
  • Интерфейсы в Python: в Python нет явных интерфейсов, но их роль выполняют абстрактные базовые классы (ABC) или протоколы.
  • Инверсия зависимостей: не путать с внедрением зависимостей (DI) — DI является одним из способов реализации этого принципа.

Когда использовать SOLID

Принципы SOLID полезны при проектировании средних и крупных систем, где важна поддерживаемость и расширяемость. Для небольших скриптов они могут быть избыточны, но понимание этих принципов помогает писать более чистый код в любом масштабе.

Коротко

  • SOLID — пять принципов ООП: единственная ответственность, открытость-закрытость, подстановка Лисков, разделение интерфейсов, инверсия зависимостей.
  • Каждый принцип снижает связанность и упрощает изменение кода.
  • В Python принципы реализуются через классы, наследование, абстрактные классы и композицию.
  • На собеседовании ожидайте не только перечисление, но и примеры нарушения и исправления.
как отвечать на вопрос
пример собеседования
фреймворки на собеседовании
типичные вопросы junior
интервью вопросы и ответы