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

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

Ответ

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).