Что такое принцип единственной ответственности (SRP)?

Ответ

SRP (Single Responsibility Principle) — это первый принцип SOLID, который гласит: «Класс должен иметь одну и только одну причину для изменения». Это означает, что класс должен быть ответственен за одну, четко определенную часть функциональности.

Суть принципа: Высокая связность (cohesion) внутри класса и низкая связанность (coupling) между классами. Изменение в бизнес-логике не должно влиять на код, отвечающий за сохранение данных, и наоборот.

Пример нарушения SRP:

// Класс нарушает SRP, так как совмещает три разные ответственности:
// 1. Представление данных книги.
// 2. Логика сохранения в БД.
// 3. Логика форматирования для печати.
class Book {
    private String title;
    private String author;

    // Конструкторы, геттеры, сеттеры...

    public void saveToDatabase() {
        // Логика подключения к БД и INSERT/UPDATE
        System.out.println("Saving book to database...");
    }

    public void printToConsole() {
        // Логика форматирования вывода
        System.out.println("Book: " + title + " by " + author);
    }
}

Проблемы такого подхода:

  • Если изменится способ сохранения (переход с JDBC на JPA), придется менять класс Book.
  • Если изменится формат вывода (HTML вместо консоли), снова меняется Book.
  • Класс сложно тестировать из-за смешения логики.

Рефакторинг с соблюдением SRP:

// Класс Book отвечает ТОЛЬКО за хранение данных о книге.
class Book {
    private String title;
    private String author;
    // Только данные и, возможно, валидация этих данных.
}

// Класс BookRepository отвечает ТОЛЬКО за персистентность (сохранение/загрузку).
class BookRepository {
    public void save(Book book) {
        // Вся логика работы с базой данных здесь
        System.out.println("Saving book to database...");
    }
}

// Класс BookPrinter отвечает ТОЛЬКО за представление книги.
class BookPrinter {
    public void printToConsole(Book book) {
        System.out.println("Book: " + book.getTitle() + " by " + book.getAuthor());
    }
    public String printToHtml(Book book) { /* ... */ }
}

Практические выгоды от следования SRP:

  • Упрощение тестирования: Каждый класс тестируется изолированно. BookRepository можно протестировать с mock-базой, а BookPrinter — без нее.
  • Повышение читаемости и поддерживаемости: Легче понять, за что отвечает каждый класс.
  • Снижение риска побочных эффектов: Изменение в логике печати не затронет код сохранения в БД.
  • Упрощение повторного использования: Класс Book теперь можно использовать в любом контексте, не таская за собой зависимости от базы данных или UI.

Важное уточнение: «Ответственность» — это не «один метод», а одна ось изменения (reason to change). В реальности класс может содержать несколько методов, если они относятся к одной зоне ответственности (например, класс Validator с методами validateEmail, validatePhone).

Ответ 18+ 🔞

Э, бля, слушай сюда, сейчас я тебе про SRP так расскажу, что ты сам от себя охуеешь. Это ж первый принцип из этих ваших SOLID, типа священный грааль для архитекторов, которые себя умными считают.

Короче, суть проще пареной репы: каждый класс должен заниматься своим ебаным делом, и только одним. Как будто ты на кухне: один чувак режет лук, другой жарит, а третий моет посуду. А не так, чтобы один и лук резал, и жарил, и потом ещё сковородку лизал, хитрая жопа. Формально это звучит как «класс должен иметь одну причину для изменения». То есть если бизнес-логика поменялась — ты лезешь в один класс. Если базу данных сменили — ты лезешь в другой. А не так, что из-за смены PostgreSQL на MongoDB тебе придётся ещё и логику печати на принтере переписывать, ёпта.

Смотри, какой бывает пиздец, когда этот принцип нарушают:

// Смотри на эту манду с ушами! Она всё делает.
class Book {
    private String title;
    private String author;

    // ... геттеры-сеттеры, ладно, ещё куда ни шло ...

    // О, а это что? Она уже в базу лезет сохраняться!
    public void saveToDatabase() {
        System.out.println("Saving book to database...");
    }

    // А тут она ещё и печатать умеет! Пизда рулю.
    public void printToConsole() {
        System.out.println("Book: " + title + " by " + author);
    }
}

Представляешь эту хуйню? Этот класс — тот самый распиздяй, который везде суёт свой нос.

  • Захотел ты с JSON работать вместо базы — придётся этот Book пилить.
  • Захотел выводить не в консоль, а в HTML — опять за него браться.
  • Протестировать эту хрень? Да ты её ниоткуда не вытащишь, она ко всему прилипла! Доверия ебать ноль к такому коду.

Ну и как это по-человечески делать? Да ебать мои старые костыли, элементарно же! Разделяем эту кашу на нормальные, вменяемые куски.

// Класс Book. Он теперь как монах — только за данные отвечает. Ничего не знает, никуда не лезет.
class Book {
    private String title;
    private String author;
    // Всё. Сидит себе, данные хранит. Молодец.
}

// Класс BookRepository. Его удел — долбиться с базой данных. Сохраняй, загружай, обновляй.
class BookRepository {
    public void save(Book book) {
        System.out.println("Saving book to database...");
        // Тут вся его боль, его JDBC, его Hibernate.
    }
}

// Класс BookPrinter. Его задача — красиво показать книжку. Куда угодно: в консоль, в HTML, на хуй в телеграм.
class BookPrinter {
    public void printToConsole(Book book) {
        System.out.println("Book: " + book.getTitle() + " by " + book.getAuthor());
    }
    public String printToHtml(Book book) { /* ... */ }
}

Вот видишь разницу? Теперь у каждого своя жизнь. И какие плюсы-то, бля?

  • Тестировать — одно удовольствие. Хочешь проверить логику сохранения? Подсовываешь BookRepository фейковую базу и го. BookPrinter тестируешь вообще без всего, просто даёшь ему объект.
  • Понимать код в разы легче. Открыл класс — и сразу ясно, че он делает. Никаких сюрпризов.
  • Менять что-то одно не взрывает пол-проекта. Сменили вёрстку? Правим только BookPrinter. Пофиг на базу и на сами данные.
  • Переиспользовать можно. Класс Book теперь голый и чистый, тащи его куда хочешь: в веб, в десктоп, в мобилу. Он ни к чему не привязан.

И да, важный момент, а то некоторые умники начинают дробить всё подряд: «ответственность» — это не «один метод». Это одна ось изменений. Вот есть, допустим, класс Validator. У него могут быть методы validateEmail, validatePhone, validatePassword. И это нормально! Это всё одна ответственность — валидация данных. Меняются правила валидации — меняем этот один класс. Всё логично.

Вот и весь принцип. Не пытайся сделать из класса универсального солдата, который и стреляет, и готовит, и стихи пишет. Получится хуй в пальто, а не архитектура.