Что такое 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, потому что он одновременно отвечает за сохранение данных и за логирование. Это усложняет тестирование и изменение каждой из функций.
// Плохо: класс совмещает две ответственности
class User {
save() { /* сохранение в БД */ }
log() { /* запись в лог */ }
}
// Хорошо: каждая ответственность вынесена в отдельный класс
class User {
save() { /* сохранение в БД */ }
}
class Logger {
log(message) { /* запись в лог */ }
}
### 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 — классическая ошибка.
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.
// Плохо: один большой интерфейс
class Worker {
work() {}
eat() {}
sleep() {}
}
// Хорошо: разделение на специализированные роли
class Workable {
work() {}
}
class Eatable {
eat() {}
}DIP: зависимость от абстракций
В Node.js это часто реализуется через внедрение зависимостей. Вместо создания конкретного экземпляра внутри класса, он получает его извне.
// Плохо: жёсткая связь с конкретной реализацией
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: зависимость от абстракций.
- Применяйте принципы осознанно, избегая излишней сложности.
