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

Что такое SOLID

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

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

Каждая буква аббревиатуры соответствует одному принципу:

  • S — Single Responsibility Principle (SRP): класс должен иметь только одну причину для изменения, то есть выполнять одну задачу.
  • O — Open/Closed Principle (OCP): программные сущности должны быть открыты для расширения, но закрыты для модификации.
  • L — Liskov Substitution Principle (LSP): подклассы должны корректно заменять свои базовые классы, не нарушая работу программы.
  • I — Interface Segregation Principle (ISP): клиенты не должны зависеть от интерфейсов, которые они не используют.
  • D — Dependency Inversion Principle (DIP): зависимости должны строиться на абстракциях, а не на конкретных реализациях.

Примеры применения в Node.js

Рассмотрим каждый принцип на практических примерах.

SRP: разделение ответственности

Класс User в примере ниже нарушает SRP, потому что он одновременно отвечает за сохранение данных и за логирование. Это усложняет тестирование и изменение каждой из функций.

javascript
// Плохо: класс совмещает две ответственности
class User {
  save() { /* сохранение в БД */ }
  log() { /* запись в лог */ }
}

// Хорошо: каждая ответственность вынесена в отдельный класс
class User {
save() { /* сохранение в БД */ }
}

class Logger {
log(message) { /* запись в лог */ }
}

text

### OCP: расширение без изменения

Допустим, есть функция расчёта площади фигур. Если добавлять новую фигуру, придётся менять существующий код — это нарушение OCP. Правильнее использовать полиморфизм.

```javascript
// Плохо: при добавлении новой фигуры нужно менять функцию
function area(shape) {
  if (shape.type === 'circle') return Math.PI * shape.radius ** 2;
  if (shape.type === 'square') return shape.side ** 2;
}

// Хорошо: каждая фигура сама знает свою площадь
class Circle {
  constructor(radius) { this.radius = radius; }
  area() { return Math.PI * this.radius ** 2; }
}

class Square {
  constructor(side) { this.side = side; }
  area() { return this.side ** 2; }
}

LSP: корректное наследование

Если подкласс изменяет поведение базового класса так, что это ломает ожидания клиента, это нарушение LSP. Например, класс Rectangle и его наследник Square — классическая ошибка.

javascript
class Rectangle {
  setWidth(w) { this.width = w; }
  setHeight(h) { this.height = h; }
  area() { return this.width * this.height; }
}

// Плохо: квадрат меняет поведение родителя
class Square extends Rectangle {
  setWidth(w) { this.width = w; this.height = w; }
  setHeight(h) { this.height = h; this.width = h; }
}

ISP: узкие интерфейсы

В JavaScript нет формальных интерфейсов, но принцип применим к структуре объектов и классов. Если класс требует от клиента реализовать много методов, часть из которых не нужна, это нарушение ISP.

javascript
// Плохо: один большой интерфейс
class Worker {
  work() {}
  eat() {}
  sleep() {}
}

// Хорошо: разделение на специализированные роли
class Workable {
  work() {}
}
class Eatable {
  eat() {}
}

DIP: зависимость от абстракций

В Node.js это часто реализуется через внедрение зависимостей. Вместо создания конкретного экземпляра внутри класса, он получает его извне.

javascript
// Плохо: жёсткая связь с конкретной реализацией
class UserService {
  constructor() {
    this.db = new MySQLDatabase();
  }
}

// Хорошо: зависимость от абстракции (интерфейса)
class UserService {
  constructor(db) {
    this.db = db; // db — любой объект с методами save/find
  }
}

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

  • Чрезмерное дробление: следование SRP не означает, что нужно создавать класс на каждый метод. Ищите разумный баланс.
  • Наследование вместо композиции: LSP часто нарушается при глубоких иерархиях. Предпочитайте композицию.
  • Игнорирование контекста: SOLID — это рекомендации, а не догма. В небольших проектах строгое следование всем принципам может быть избыточным.
  • Путаница между OCP и DIP: OCP касается расширения поведения, DIP — управления зависимостями. Они связаны, но не идентичны.

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

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

Коротко

  • SOLID — пять принципов ООП: SRP, OCP, LSP, ISP, DIP.
  • SRP: одна ответственность у класса; OCP: расширение без изменения кода.
  • LSP: подклассы не должны ломать поведение базового класса.
  • ISP: клиенты не зависят от ненужных методов; DIP: зависимость от абстракций.
  • Применяйте принципы осознанно, избегая излишней сложности.
как отвечать на вопрос
пример собеседования
фреймворки на собеседовании
типичные вопросы junior
интервью вопросы и ответы