Что такое принципы SOLID в ООП?

«Что такое принципы SOLID в ООП?» — вопрос из категории ООП, который задают на 26% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

SOLID — это акроним пяти фундаментальных принципов объектно-ориентированного проектирования и программирования, направленных на создание понятного, гибкого и поддерживаемого кода.

1. Принцип единственной ответственности (Single Responsibility Principle - SRP)

«Один класс — одна причина для изменения». Класс должен решать только одну задачу и нести ответственность только за одну часть функциональности.

// НЕВЕРНО: Класс отвечает и за логику заказа, и за его сохранение, и за логирование.
class OrderProcessor {
    void process(Order order) {
        // Валидация заказа
        // Сохранение в БД
        // Отправка email
        // Логирование
    }
}

// ВЕРНО: Ответственности разделены.
class OrderValidator { /* Только валидация */ }
class OrderRepository { /* Только работа с БД */ }
class EmailService { /* Только отправка писем */ }
class Logger { /* Только логирование */ }

class OrderProcessor {
    private OrderValidator validator;
    private OrderRepository repository;
    // ... внедрение зависимостей
    void process(Order order) {
        validator.validate(order);
        repository.save(order);
        // ...
    }
}

2. Принцип открытости/закрытости (Open/Closed Principle - OCP)

«Классы должны быть открыты для расширения, но закрыты для модификации». Новую функциональность следует добавлять через создание новых классов (наследование, композиция), а не изменяя существующий код.

// НЕВЕРНО: При добавлении новой фигуры нужно менять метод area().
class AreaCalculator {
    public double area(Object shape) {
        if (shape instanceof Rectangle) { /* расчёт */ }
        else if (shape instanceof Circle) { /* расчёт */ }
        // Добавление нового `if` для Triangle
    }
}

// ВЕРНО: Используем абстракцию.
interface Shape { double area(); }

class Rectangle implements Shape { /* реализация area() */ }
class Circle implements Shape { /* реализация area() */ }
class Triangle implements Shape { /* НОВЫЙ КЛАСС, не требует изменения AreaCalculator */ }

class AreaCalculator {
    public double area(Shape shape) { // Код закрыт для модификации
        return shape.area(); // Полиморфный вызов
    }
}

3. Принцип подстановки Барбары Лисков (Liskov Substitution Principle - LSP)

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

// НАРУШЕНИЕ LSP: Квадрат не может быть заменой Прямоугольника, если меняется логика установки сторон.
class Rectangle {
    protected int width, height;
    void setWidth(int w) { width = w; }
    void setHeight(int h) { height = h; }
}
class Square extends Rectangle {
    @Override
    void setWidth(int w) { width = height = w; } // Неожиданное поведение для "прямоугольника"
}
// Клиентский код, ожидающий Rectangle, сломается.

4. Принцип разделения интерфейса (Interface Segregation Principle - ISP)

«Много специализированных интерфейсов лучше одного общего». Клиенты не должны зависеть от методов, которые они не используют.

// ПЛОХО: Принтер вынужден реализовывать ненужный ему метод scan().
interface MultiFunctionDevice {
    void print();
    void scan();
    void fax();
}
class SimplePrinter implements MultiFunctionDevice {
    public void print() { /* OK */ }
    public void scan() { throw new UnsupportedOperationException(); } // Ненужный метод!
    public void fax() { throw new UnsupportedOperationException(); } // Ненужный метод!
}

// ХОРОШО: Интерфейсы разделены.
interface Printer { void print(); }
interface Scanner { void scan(); }
interface Fax { void fax(); }

class SimplePrinter implements Printer { /* реализует ТОЛЬКО print() */ }
class AllInOnePrinter implements Printer, Scanner, Fax { /* реализует всё */ }

5. Принцип инверсии зависимостей (Dependency Inversion Principle - DIP)

«Зависимости должны строиться на абстракциях, а не на деталях».

  1. Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба должны зависеть от абстракций.
  2. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
    
    // НЕВЕРНО: Класс верхнего уровня жёстко зависит от конкретной реализации нижнего уровня.
    class OrderService {
    private MySQLOrderRepository repository = new MySQLOrderRepository(); // Прямая зависимость
    // ...
    }

// ВЕРНО: Зависимость от абстракции (интерфейса). Конкретная реализация внедряется извне. interface OrderRepository { void save(Order order); } class MySQLOrderRepository implements OrderRepository { / ... / } class MongoOrderRepository implements OrderRepository { / ... / }

class OrderService { // Модуль верхнего уровня private final OrderRepository repository; // Зависимость от абстракции

// Внедрение зависимости через конструктор (Dependency Injection)
public OrderService(OrderRepository repository) {
    this.repository = repository;
}

public void processOrder(Order order) {
    repository.save(order); // Работает с любым OrderRepository
}

}


**Итог:** Следование SOLID делает код менее связанным, более тестируемым, адаптивным к изменениям и упрощает командную разработку.