Перейти к основному содержимому

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

Single Responsibility Principle, SRP

«Модуль должен иметь одну и только одну причину для изменения. Пользователи и заинтересованные лица и есть та самая причина для изменений». (с) «Чистая архитектура», глава 7, страница 79

Уточнение

Здесь и далее Модуль это Класс или Функция.

Определим, что означает слово «модуль». Самое простое определение — файл с исходным кодом. В большинстве случаев это определение можно принять. Однако некоторые языки среды разработки не используют исходные файлы для хранения кода. В таких случаях модуль — это просто связный набор функций и структур данных .

Функция должна делать что-то одно и только одно. Внимание, это не принцип единственной ответственности!

к сведению

Модуль должен иметь одну и только одну причину для изменения.

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

к сведению

Модуль должен отвечать за одного и только за одного пользователя или заинтересованное лицо.

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

Более правильным выглядит понятие группы, состоящей из одного или нескольких лиц, желающих данного изменения. Мы будем называть такие группы акторами (actor).

к сведению

Модуль должен отвечать за одного и только за одного актора.

Признаки наружения данного принципа:

Признак 1: непреднамеренное дублирование

class Employee {
calculatePay() {}
reportHours() {}
save() {}
}

Этот класс нарушает принцип единственной ответственности, потому что три его метода отвечают за три разных актора.

  • Реализация метода calculatePay() определяется бухгалтерией.
  • Реализация метода reportHours() определяется и используется отделом по работе с персоналом.
  • Реализация метода save() определяется администраторами баз данных.

Например, представьте, что функции calculatePay() и reportHours() используют общий алгоритм расчета не сверхурочных часов. Представьте также, что разработчики, старающиеся не дублировать код, поместили реализацию этого алгоритма в функцию с именем regularHours().

class Employee {
calculatePay() {
this.regularHours()
}
reportHours() {
this.regularHours()
}
save() {}
private regularHours() {}
}

В итоге, если поменяется алгоритм regularHours() для бухгалтерии, то есть для calculatePay(), то функция reportHours() будет работать с ошибками.

Эти проблемы возникают из-за того, что мы вводим в работу код, от которого зависят разные акторы. Принцип единственной ответственности требует разделять код, от которого зависят разные акторы.

Признак 2: слияния

Два разработчика извлекли из репозитория код класса Employee. Первый разработчик изменяет код save(). Второй - код reportHours().

Допустим их изменения не совместимы и это значит далее потребуется разрешить конфлик слияния кода. При слиянии все три функции подвергаются риску, в том числе и calculatePay().

Существует много других признаков, которые мы могли бы рассмотреть, но все они сводятся к изменению одного и того же исходного кода разными людьми по разным причинам.

Решение

Существует много решений этой проблемы . Но каждое связано с перемещением функций в разные классы.

class EmployeeData {}
class PayCalculator {
constructor(private employeeData: EmployeeData) {}
calculatePay() {}
}
class HourReporter {
constructor(private employeeData: EmployeeData) {}
reportHours() {}
}
class EmployeeSaver {
constructor(private employeeData: EmployeeData) {}
save() {}
}

Недостаток такого решения — разработчик теперь должен создавать экземпляры трех классов и следить за ними. Эта проблема часто решается приме- нением шаблона проектирования «Фасад» (Facade).

Класс EmployeeFacade содержит очень немного кода и отвечает за создание экземпляров трех классов и делегирование вызовов методов.

В качестве фасада можно также использовать исходный класс Employee.

class EmployeeFacade {
constructor(private employeeData: EmployeeData) {}
calculatePay() {
return new PayCalculator(this.employeeData).calculatePay()
}
reportHours() {
return new HourReporter(this.employeeData).calculatePay()
}
save() {
return new EmployeeSaver(this.employeeData).calculatePay()
}
}

Заключение

Принцип единственной ответственности (Single Responsibility Principle; SRP) касается функций и классов, но он проявляется в разных формах на еще двух более высоких уровнях.

На уровне компонентов он превращается в принцип согласованного изменения (Common Closure Principle; CCP), а на архитектурном уровне — в принцип оси изменения (Axis of Change), отвечающий за создание архитектурных границ.

Источники

Мартин Р. Чистая архитектура. Искусство разработки программного обеспечения.