Что знаешь про принципы 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).
-
Нарушение хотя бы одного из принципов может привести к «хрупкому» коду, где любое изменение вызывает каскадную волну багов и переписывания.