Расшифруй аббревиатуру SOLID

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

Ответ

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

  1. S — Single Responsibility Principle (Принцип единственной ответственности). Класс или модуль должен иметь одну и только одну причину для изменения. В контексте Node.js это часто означает разделение логики: отдельный модуль для работы с базой данных, отдельный — для бизнес-логики, отдельный — для валидации.

    // Плохо: Класс UserService занимается и логикой, и отправкой email.
    // Хорошо: Выносим отправку email в отдельный сервис NotificationService.
    class UserService {
      constructor(userRepository, notificationService) {
        this.userRepository = userRepository;
        this.notificationService = notificationService;
      }
      async register(userData) {
        const user = await this.userRepository.create(userData);
        await this.notificationService.sendWelcomeEmail(user.email);
        return user;
      }
    }
  2. O — Open/Closed Principle (Принцип открытости/закрытости). Сущности должны быть открыты для расширения, но закрыты для модификации. Достигается через использование абстракций (интерфейсов) и паттернов вроде Стратегии.

    // Интерфейс для обработчиков платежей
    interface PaymentProcessor {
      process(amount: number): Promise<void>;
    }
    // Чтобы добавить новый способ оплаты (расширить), мы создаем новый класс,
    // не меняя существующий код, который использует PaymentProcessor.
  3. L — Liskov Substitution Principle (Принцип подстановки Лисков). Объекты в программе должны быть заменяемыми на экземпляры их подтипов без изменения правильности программы. Наследник не должен ужесточать предусловия или ослаблять постусловия базового класса.

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

    // Вместо одного большого интерфейса для репозитория:
    // interface Repository { create(); read(); update(); delete(); generateReport(); }
    // Лучше разделить:
    interface CrudRepository { create(); read(); update(); delete(); }
    interface ReportGenerator { generateReport(); }
  5. D — Dependency Inversion Principle (Принцип инверсии зависимостей). Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба должны зависеть от абстракций. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций. В Node.js это реализуется через внедрение зависимостей (DI).

    // Прямая зависимость (плохо):
    // class OrderService { constructor() { this.paymentGateway = new PayPalGateway(); } }
    
    // Зависимость от абстракции (хорошо):
    class OrderService {
      constructor(paymentGateway) { // Принимаем интерфейс/абстракцию
        this.paymentGateway = paymentGateway;
      }
    }
    // Теперь OrderService зависит от абстракции платежного шлюза, а не от конкретной реализации PayPal.