Что знаешь про принципы SOLID


Принципы SOLID — это набор из пяти основных принципов объектно-ориентированного программирования, направленных на создание устойчивой, масштабируемой и легко сопровождаемой архитектуры программного обеспечения. Эти принципы были сформулированы Робертом Мартином (Robert C. Martin, «Uncle Bob») и широко используются при проектировании программных систем, особенно в контексте ООП и архитектурных паттернов.

S — Single Responsibility Principle (Принцип единственной ответственности)

Объект или класс должен иметь только одну причину для изменения, то есть выполнять только одну задачу.

Пояснение:
Класс должен быть ответственен за что-то одно. Например, класс InvoicePrinter должен отвечать только за печать счета, а не за его расчёт или сохранение в базу данных. Если класс берёт на себя несколько обязанностей, это приводит к сильной связности и затрудняет модификации.

Нарушение:

class Report {
void generate() {...}
void print() {...}
void saveToDatabase() {...}
}

Всё перемешано: генерация, вывод, сохранение.

Правильный подход:
Разделить на классы: ReportGenerator, ReportPrinter, ReportSaver.

O — Open/Closed Principle (Принцип открытости/закрытости)

Программные сущности (классы, модули, функции) должны быть открыты для расширения, но закрыты для модификации.

Пояснение:
Когда нужно изменить поведение, лучше использовать наследование, делегирование или внедрение зависимостей, а не изменять уже существующий код. Это позволяет избежать поломок в рабочей системе.

Пример:

interface Shape {
double area();
}
class Circle implements Shape { ... }
class Rectangle implements Shape { ... }
class AreaCalculator {
double totalArea(List<Shape> shapes) {
return shapes.stream().mapToDouble(Shape::area).sum();
}
}

Чтобы добавить новый тип фигуры, не нужно менять AreaCalculator.

L — Liskov Substitution Principle (Принцип подстановки Барбары Лисков)

Объекты подкласса должны быть взаимозаменяемыми с объектами суперкласса без нарушения логики программы.

Пояснение:
Если S является подклассом T, то объекты типа T могут быть заменены объектами типа S без нежелательных эффектов. Подкласс должен соблюдать контракт базового класса.

Нарушение:

class Bird {
void fly() { ... }
}
class Ostrich extends Bird {
void fly() {
throw new UnsupportedOperationException();
}
}

Ostrich не может летать, но является Bird, это нарушает принцип.

Решение:
Выделить FlyingBird и NonFlyingBird, чтобы не навязывать поведение.

I — Interface Segregation Principle (Принцип разделения интерфейса)

Клиенты не должны зависеть от интерфейсов, которые они не используют.

Пояснение:
Лучше иметь много узкоспециализированных интерфейсов, чем один универсальный. Это делает систему гибкой и облегчает сопровождение.

Нарушение:

interface Worker {
void work();
void eat();
}
class Robot implements Worker {
void work() { ... }
void eat() { throw new UnsupportedOperationException(); }
}

Robot не ест, но обязан реализовать eat.

Решение:
Разделить на Workable и Eatable.

D — Dependency Inversion Principle (Принцип инверсии зависимостей)

Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба типа должны зависеть от абстракций. Абстракции не должны зависеть от деталей; детали должны зависеть от абстракций.

Пояснение:
Вместо создания зависимостей напрямую, компоненты должны использовать интерфейсы. Это позволяет легко изменять реализацию и упрощает тестирование.

Нарушение:

class FileLogger {
void log(String msg) { ... }
}
class App {
FileLogger logger = new FileLogger();
}

Правильный подход:

interface Logger {
void log(String msg);
}
class FileLogger implements Logger { ... }
class App {
Logger logger;
App(Logger logger) {
this.logger = logger;
}
}

Теперь можно подставить любой Logger, включая мок-объект в тестах.

Дополнительные замечания

  • Принципы SOLID не зависят от языка программирования, но особенно хорошо применяются в ООП-языках: Java, C#, C++, Kotlin и др.

  • Эти принципы лежат в основе чистой архитектуры, DIP, DI-контейнеров, модульности, тестируемости, паттернов проектирования (например, Strategy, Decorator).

  • Нарушение хотя бы одного из принципов может привести к «хрупкому» коду, где любое изменение вызывает каскадную волну багов и переписывания.