Ответ
SOLID — это набор из пяти фундаментальных принципов объектно-ориентированного проектирования, направленных на создание понятного, гибкого и поддерживаемого кода.
1. Принцип единственной ответственности (Single Responsibility Principle - SRP)
Один класс должен иметь одну и только одну причину для изменения.
Класс должен отвечать за одну конкретную задачу или функциональность.
Пример нарушения и исправления:
// НАРУШЕНИЕ: Класс отвечает и за работу с данными, и за логирование.
class UserService {
public void saveUser(User user) {
// ... сохранение в БД
}
public void logError(String message) {
// ... запись в лог-файл
}
}
// СОБЛЮДЕНИЕ: Ответственности разделены.
class UserRepository { // Отвечает только за данные
public void save(User user) { /*...*/ }
}
class Logger { // Отвечает только за логирование
public void log(String message) { /*...*/ }
}
class UserService { // Координирует работу
private UserRepository repository;
private Logger logger;
public void saveUser(User user) {
try {
repository.save(user);
} catch (Exception e) {
logger.log(e.getMessage());
}
}
}
2. Принцип открытости/закрытости (Open/Closed Principle - OCP)
Программные сущности должны быть открыты для расширения, но закрыты для модификации.
Новую функциональность следует добавлять путем создания нового кода, а не изменяя существующий.
Пример: Использование интерфейсов и полиморфизма для добавления новых типов фигур без изменения кода, который вычисляет общую площадь.
3. Принцип подстановки Барбары Лисков (Liskov Substitution Principle - LSP)
Объекты в программе должны быть заменяемыми на экземпляры их подтипов без изменения правильности программы.
Наследующий класс должен дополнять, а не изменять поведение базового класса.
Пример нарушения: Класс Square, наследующий от Rectangle. Если у Rectangle есть сеттеры для ширины и высоты по отдельности, то у Square их изменение нарушит инвариант (равенство сторон). Это нарушает LSP, так как код, работающий с Rectangle, сломается при передаче Square.
4. Принцип разделения интерфейса (Interface Segregation Principle - ISP)
Много специализированных интерфейсов лучше, чем один универсальный.
Клиенты не должны зависеть от методов, которые они не используют.
Пример:
// ПЛОХО: "Толстый" интерфейс
interface Worker {
void work();
void eat();
void sleep();
}
// Класс Robot вынужден реализовывать eat() и sleep(), которые ему не нужны.
// ХОРОШО: Разделенные интерфейсы
interface Workable {
void work();
}
interface Eatable {
void eat();
}
interface Sleepable {
void sleep();
}
class Human implements Workable, Eatable, Sleepable { /*...*/ }
class Robot implements Workable { /*...*/ } // Реализует только нужное
5. Принцип инверсии зависимостей (Dependency Inversion Principle - DIP)
Зависимости должны строиться на абстракциях, а не на деталях.
- Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба должны зависеть от абстракций.
- Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
Пример (внедрение зависимости через конструктор):
// Абстракция (интерфейс)
interface MessageService {
void sendMessage(String message);
}
// Деталь реализации 1
class EmailService implements MessageService {
public void sendMessage(String message) { /* отправка email */ }
}
// Деталь реализации 2
class SMSService implements MessageService {
public void sendMessage(String message) { /* отправка SMS */ }
}
// Модуль верхнего уровня зависит от абстракции, а не от конкретного сервиса.
class NotificationService {
private MessageService service;
// Внедрение зависимости (Dependency Injection)
public NotificationService(MessageService service) {
this.service = service;
}
public void notify(String message) {
service.sendMessage(message);
}
}
// Теперь NotificationService легко тестировать с Mock-объектом и менять реализацию MessageService.
Следование SOLID облегчает модульное тестирование, рефакторинг и снижает связанность компонентов системы.