Программа курса← Объектно ориентированное программирование
1История ООП
2Объект и Класс
3Базовые концепции ООП
4Принцип проектирования GRASP
5Принцип проектирования SOLID
6Паттерны GOF

SRP – принцип единственной ответственности

TODO: отвалидировать – текст собран автоматически из материалов курса на Google Drive, проверьте формулировки и код.

Класс должен иметь только одну причину для изменения. Иными словами: один класс – одна обязанность.

Если объект делает слишком много, его сложнее поддерживать и изменять, а правка в одной части может сломать другую.

Причина для изменения, а не количество методов

Формулировку часто понимают как «класс должен быть маленьким». Это не так: важна не длина, а источник изменений. Спросите себя, кто может прийти с требованием поменять этот класс. Если таких заказчиков двое – бухгалтерия и отдел вёрстки писем, – обязанностей тоже две.

// Две причины меняться: правила расчёта зарплаты и формат отчёта
public class Employee
{
    public decimal CalculatePay() => 0;
    public string ExportToCsv() => "";
}

// Каждая причина живёт в своём классе
public class Employee
{
    public decimal CalculatePay() => 0;
}

public class EmployeeCsvExporter
{
    public string Export(Employee employee) => "";
}
// Две причины меняться: правила расчёта зарплаты и формат отчёта
class Employee {
public:
    double CalculatePay() const { return 0; }
    std::string ExportToCsv() const { return ""; }
};

// Каждая причина живёт в своём классе
class Employee {
public:
    double CalculatePay() const { return 0; }
};

class EmployeeCsvExporter {
public:
    std::string Export(const Employee& employee) const { return ""; }
};
// Две причины меняться: правила расчёта зарплаты и формат отчёта
type Employee struct{}

func (Employee) CalculatePay() float64 { return 0 }
func (Employee) ExportToCSV() string   { return "" }

// Каждая причина живёт в своём типе
type Employee struct{}

func (Employee) CalculatePay() float64 { return 0 }

type EmployeeCSVExporter struct{}

func (EmployeeCSVExporter) Export(e Employee) string { return "" }
# Две причины меняться: правила расчёта зарплаты и формат отчёта
class Employee:
    def calculate_pay(self) -> float:
        return 0

    def export_to_csv(self) -> str:
        return ""

# Каждая причина живёт в своём классе
class Employee:
    def calculate_pay(self) -> float:
        return 0

class EmployeeCsvExporter:
    def export(self, employee: Employee) -> str:
        return ""
// Две причины меняться: правила расчёта зарплаты и формат отчёта
class Employee {
    calculatePay(): number {
        return 0;
    }

    exportToCsv(): string {
        return "";
    }
}

// Каждая причина живёт в своём классе
class Employee {
    calculatePay(): number {
        return 0;
    }
}

class EmployeeCsvExporter {
    export(employee: Employee): string {
        return "";
    }
}
// Две причины меняться: правила расчёта зарплаты и формат отчёта
public class Employee {
    public BigDecimal calculatePay() {
        return BigDecimal.ZERO;
    }

    public String exportToCsv() {
        return "";
    }
}

// Каждая причина живёт в своём классе
public class Employee {
    public BigDecimal calculatePay() {
        return BigDecimal.ZERO;
    }
}

public class EmployeeCsvExporter {
    public String export(Employee employee) {
        return "";
    }
}
// Две причины меняться: правила расчёта зарплаты и формат отчёта
class Employee {
    fun calculatePay(): BigDecimal = BigDecimal.ZERO
    fun exportToCsv(): String = ""
}

// Каждая причина живёт в своём классе
class Employee {
    fun calculatePay(): BigDecimal = BigDecimal.ZERO
}

class EmployeeCsvExporter {
    fun export(employee: Employee): String = ""
}

Что даёт соблюдение

  • Изменение требования затрагивает один файл. Поменялись правила начисления зарплаты – правится расчёт, и только он; формат отчёта об этом не знает.
  • Тест становится осмысленным. У класса одна причина упасть, поэтому по красному тесту сразу понятно, что именно сломалось.
  • Меньше конфликтов при слиянии. Бухгалтерия и отдел отчётности правят разные файлы, а не соседние строки одного класса.
  • Класс легко назвать. Если имя подбирается без союза «и» – ответственность одна. Это самая дешёвая проверка принципа, и работает она ещё до написания кода.

Чем платят

Классов становится больше, и по одному сценарию приходится ходить по нескольким файлам. Принцип окупается там, где код действительно меняется; дробить стабильный класс ради самого дробления смысла нет.