Принципы SOLID — это набор из пяти принципов объектно-ориентированного программирования, которые помогают улучшить дизайн кода, делая его более модульным, поддерживаемым и масштабируемым. Вот более подробное описание каждого принципа: 1. S: Принцип единственной ответственности (Single Responsibility Principle, SRP) Каждый класс должен иметь только одну ответственность или задачу, и эта задача должна быть полностью инкапсулирована в пределах класса. Это означает, что у класса должна быть только одна причина для изменения. Например, если класс отвечает за взаимодействие с базой данных и одновременно за представление данных пользователю, он нарушает SRP. • Преимущества: Такой подход уменьшает взаимозависимость между модулями, что упрощает тестирование и поддержку. Пример:

class ReportGenerator {
    public void generateReport() {
        // генерация отчета
    }
}

class ReportPrinter {
    public void printReport() {
        // вывод отчета
    }
}
Здесь каждый класс выполняет только одну задачу: ReportGenerator генерирует отчет, а ReportPrinter выводит его. ___________________________________________________________________________________________________________ 2. O: Принцип открытости/закрытости (Open/Closed Principle, OCP) Классы должны быть открыты для расширения, но закрыты для изменения. Это означает, что при добавлении новой функциональности не следует изменять существующий код, а лучше расширять его через наследование или другие механизмы. • Преимущества: Это снижает риск ошибок в существующем коде при добавлении нового функционала и повышает устойчивость системы. Пример:

abstract class Shape {
    abstract void draw();
}

class Circle extends Shape {
    @Override
    void draw() {
        // рисование круга
    }
}

class Square extends Shape {
    @Override
    void draw() {
        // рисование квадрата
    }
}
Теперь, если нам нужно добавить новый тип фигуры, мы просто создаем новый подкласс Shape, не изменяя существующие классы. ___________________________________________________________________________________________________________ 3. L: Принцип подстановки Барбары Лисков (Liskov Substitution Principle, LSP) Объекты должны быть заменяемы экземплярами их подклассов без нарушения корректности программы. Наследник должен сохранять поведение родителя, чтобы его можно было использовать вместо родительского класса без неожиданного поведения. • Преимущества: Этот принцип помогает избежать ошибок при работе с полиморфизмом. Пример нарушения LSP:

class Bird {
    public void fly() {
        // логика полета
    }
}

class Penguin extends Bird {
    @Override
    public void fly() {
        throw new UnsupportedOperationException("Пингвины не летают!");
    }
}
Здесь класс Penguin, являющийся наследником Bird, нарушает принцип Лисков, так как не может выполнять операцию fly. Чтобы это исправить, можно ввести базовый класс, разделяющий летающих и не летающих птиц. ___________________________________________________________________________________________________________ 4. I: Принцип разделения интерфейса (Interface Segregation Principle, ISP) Клиенты не должны зависеть от интерфейсов, которые они не используют. Другими словами, лучше иметь несколько специфичных интерфейсов, чем один универсальный, который заставляет реализовать ненужные методы. • Преимущества: Это улучшает модульность и снижает связанность кода. Пример:

interface Worker {
    void work();
    void eat();
}
class Robot implements Worker {
    @Override
    public void work() {
        // работает
    }

    @Override
    public void eat() {
        // роботу не нужно есть
    }
}
Это пример нарушения ISP. Лучше разделить интерфейсы на Worker (для работы) и Eater (для еды). ___________________________________________________________________________________________________________ 5. D: Принцип инверсии зависимостей (Dependency Inversion Principle, DIP) Высокоуровневые модули не должны зависеть от низкоуровневых; оба должны зависеть от абстракций. Это означает, что детали реализации должны быть отделены от логики. Важная часть этого принципа — внедрение зависимостей (dependency injection), которое помогает избежать жесткой зависимости от конкретных реализаций. • Преимущества: Принцип упрощает тестирование и рефакторинг, так как изменять детали реализации можно, не затрагивая основной код. Пример:

interface Database {
    void saveData(String data);
}

class MySQLDatabase implements Database {
    public void saveData(String data) {
        // сохранение данных в MySQL
    }
}

class DataHandler {
    private Database database;

    public DataHandler(Database database) {
        this.database = database;
    }

    public void save(String data) {
        database.saveData(data);
    }
}
Здесь DataHandler зависит от интерфейса Database, а не от конкретной реализации базы данных, что позволяет легко менять базу данных (например, с MySQL на PostgreSQL) без изменения основной логики программы.