SOLID

S — Single Responsibility  — один класс/функция = одна ответственность
O — Open/Closed            — открыт для расширения, закрыт для изменения
L — Liskov Substitution    — подкласс можно подставить вместо родителя
I — Interface Segregation  — лучше много мелких интерфейсов, чем один большой
D — Dependency Inversion   — зависеть от абстракций, не от реализаций

S — Single Responsibility Principle

Принцип единственной ответственности

Смысл

Каждый модуль, класс или функция должна делать одно дело и иметь только одну причину для изменения.

Индикаторы нарушения

  • Функция делает fetch + transform + render
  • Компонент содержит логику вида «если сумма > 5000 — применить скидку» — это бизнес-решение, оно не должно жить в UI
// ❌ Нарушение — функция делает всё сразу
const processUser = (user) => {
    validateUser(user);
    saveToDatabase(user);
    sendEmail(user);
};
 
// ✅ Каждая функция делает одно
const validate = (user) => { /* ... */ };
const save     = (user) => { /* ... */ };
const notify   = (user) => { /* ... */ };

O — Open/Closed Principle

Принцип открытости/закрытости

Смысл

Модуль должен быть открыт для расширения, но закрыт для изменения. Новая фича — добавляем, не правим существующий код.

Индикаторы нарушения

  • Каждый новый кейс требует if/else или правки существующего компонента
// ❌ Нарушение — добавление нового типа требует изменения функции
const getArea = (shape) => {
    if (shape.type === 'circle') return Math.PI * shape.r ** 2;
    if (shape.type === 'square') return shape.side ** 2;
    // каждый новый тип → правка функции
};
 
// ✅ Расширяем через полиморфизм, не через if
class Circle { area() { return Math.PI * this.r ** 2; } }
class Square { area() { return this.side ** 2; } }
const getArea = (shape) => shape.area();

L — Liskov Substitution Principle

Принцип подстановки Лисков

Смысл

Дочерний тип можно подставить вместо родительского, и программа не сломается. Дочерний обязан выполнять всё то, что обещает родительский — не урезая поведение и не нарушая его ожиданий.

Индикаторы нарушения

  • Подкласс переопределяет метод так, что сужает контракт или бросает исключение там, где родитель работал (классический Square extends Rectangle: setWidth ломает ожидания для Rectangle)
  • Приходится проверять instanceof ConcreteClass и ветвить логику вместо единообразной работы через интерфейс — значит, подтипы не взаимозаменяемы

I — Interface Segregation Principle

Принцип разделения интерфейсов

Смысл

Модуль не должен зависеть от того, что ему не нужно. Если интерфейс слишком большой — раздели его на меньшие, чтобы каждый модуль получал только то, с чем реально работает.

Индикаторы нарушения

  • Класс вынужден реализовывать методы интерфейса, которые ему не нужны (пустые заглушки или throw new Error('not supported'))
  • «Толстый» интерфейс: клиент зависит от объекта целиком, хотя использует 1–2 метода/поля — стоит разбить его на узкие интерфейсы

D — Dependency Inversion Principle

Принцип инверсии зависимостей

Смысл

Модули не должны напрямую зависеть друг от друга — между верхним и нижним уровнем встаёт абстракция (интерфейс или тип). Верхний уровень описывает что нужно через интерфейс, нижний реализует его — но они не знают друг о друге напрямую.

Индикаторы нарушения

  • Компонент импортирует fetch или работает с сырым API-ответом напрямую
// ❌ Зависим от конкретной реализации
class OrderService {
    complete(order) {
        new EmailSender().send(order.email, 'Done!'); // жёсткая зависимость
    }
}
 
// ✅ Зависим от абстракции (любой mailer с методом send)
class OrderService {
    constructor(mailer) { this.mailer = mailer; }
    complete(order) { this.mailer.send(order.email, 'Done!'); }
}

Шпаргалка по SOLID

ПринципВопрос для проверки
S — Single ResponsibilityЭтот модуль делает только одно дело?
O — Open/ClosedРасширение добавляет новый код, не трогая существующий?
L — Liskov SubstitutionДочерний тип можно подставить вместо родительского без поломок?
I — Interface SegregationКаждый модуль получает только то, с чем реально работает?
D — Dependency InversionВерхний уровень зависит от абстракции, а не от конкретной реализации?

В React SOLID применяется на уровне компонентов: один компонент — одна ответственность, пропсы — интерфейс, кастомные хуки — инверсия зависимостей.

См. также