Расшифруйте и кратко объясните каждый из принципов SOLID

«Расшифруйте и кратко объясните каждый из принципов SOLID» — вопрос из категории ООП, который задают на 10% собеседований Python Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

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

  1. S — Single Responsibility Principle (Принцип единственной ответственности) Класс должен иметь только одну причину для изменения, то есть выполнять только одну основную задачу.

    # Неправильно: класс делает всё сразу
    class User: 
        def get_user_data(self): pass
        def save_to_db(self): pass
    
    # Правильно: обязанности разделены
    class User:
        def __init__(self, name): self.name = name
    
    class UserRepository:
        def save(self, user: User): print(f"Saving {user.name}")
  2. O — Open/Closed Principle (Принцип открытости/закрытости) Программные сущности (классы, модули) должны быть открыты для расширения, но закрыты для модификации.

    from abc import ABC, abstractmethod
    
    class Shape(ABC):
        @abstractmethod
        def area(self): pass
    
    # Расширение: добавляем новые фигуры без изменения существующего кода
    class Rectangle(Shape):
        def area(self): return 2 * 5
    
    class Circle(Shape):
        def area(self): return 3.14 * (3**2)
  3. L — Liskov Substitution Principle (Принцип подстановки Барбары Лисков) Объекты производных классов должны иметь возможность заменять объекты базовых классов, не нарушая работу программы.

    class Bird:
        def fly(self): print("I can fly")
    
    class Duck(Bird): pass # Утка - птица, она летает. OK.
    
    # Нарушение LSP: страус - птица, но не летает
    class Ostrich(Bird):
        def fly(self): raise Exception("I can't fly")
  4. I — Interface Segregation Principle (Принцип разделения интерфейса) Клиенты не должны зависеть от интерфейсов, которые они не используют. Лучше иметь много маленьких, специфичных интерфейсов, чем один большой и общий.

    class Worker(ABC):
        @abstractmethod
        def work(self): pass
    
    class Eater(ABC):
        @abstractmethod
        def eat(self): pass
    
    # Класс реализует только те интерфейсы, которые ему нужны
    class Human(Worker, Eater):
        def work(self): pass
        def eat(self): pass
    
    class Robot(Worker):
        def work(self): pass
  5. D — Dependency Inversion Principle (Принцип инверсии зависимостей) Модули верхнего уровня не должны зависеть от модулей нижнего уровня. И те, и другие должны зависеть от абстракций. Абстракции не должны зависеть от деталей.

    class Switchable(ABC): # Абстракция
        @abstractmethod
        def turn_on(self): pass
    
    class LightBulb(Switchable): # Низкоуровневый модуль
        def turn_on(self): print("LightBulb: on")
    
    class Switch: # Высокоуровневый модуль
        def __init__(self, device: Switchable):
            self.device = device
        def operate(self): self.device.turn_on()
    
    # Switch зависит от абстракции Switchable, а не от конкретного LightBulb
    bulb = LightBulb()
    switch = Switch(bulb)
    switch.operate()