По словам Роберта К. Мартина, SRP заявляет, что:
Никогда не должно быть более одной причины для изменения класса .
Однако в своей книге « Чистый код» , глава 3: Функции, он показывает следующий блок кода:
public Money calculatePay(Employee e) throws InvalidEmployeeType {
switch (e.type) {
case COMMISSIONED:
return calculateCommissionedPay(e);
case HOURLY:
return calculateHourlyPay(e);
case SALARIED:
return calculateSalariedPay(e);
default:
throw new InvalidEmployeeType(e.type);
}
}
И тогда говорится:
Есть несколько проблем с этой функцией. Во-первых, он большой, и когда добавляются новые типы сотрудников, он будет расти. Во-вторых, это очень четко делает больше, чем одно. В-третьих, он нарушает принцип единой ответственности (SRP), потому что есть несколько причин для его изменения . [Акцент мой]
Во-первых, я думал, что SRP был определен для классов, но оказалось, что он также применим к функциям. Во-вторых, как эта функция имеет более чем одну причину для изменения ? Я могу видеть только его изменение из-за изменения на сотрудника.
Ответы:
Одна часто пропускаемая деталь принципа единой ответственности состоит в том, что «причины изменений» сгруппированы по субъектам прецедента (полное объяснение вы можете найти здесь ).
Итак, в вашем примере
calculatePay
метод нужно будет менять всякий раз, когда требуются новые типы сотрудников. Поскольку один тип сотрудника может не иметь ничего общего с другим, это будет нарушением принципа, если вы будете держать их вместе, так как изменение затронет различные группы пользователей (или участников варианта использования) в системе.Теперь о том, применим ли этот принцип к функциям: даже если у вас есть нарушение только в одном методе, вы все равно меняете класс по нескольким причинам, поэтому это все еще является нарушением SRP.
источник
На странице 176, Глава 12: Появление, в разделе, озаглавленном « Минимальные классы и методы», в книге приведено несколько исправлений:
и
Очевидно, он говорит о догматизме в следовании SRP, чтобы сломать совершенно прекрасные невинные маленькие методы как
calculatePay()
выше.источник
Когда мистер Мартин применяет SRP к функции, он неявно расширяет свое определение SRP. Поскольку SRP является ОО-специфической формулировкой общего принципа, и, так как это хорошая идея применительно к функциям, я не вижу проблемы с этим (хотя было бы неплохо, если бы он явно включил ее в определение).
Я также не вижу более одной причины для изменения, и я не верю, что полезно думать о ПСП в терминах «обязанностей» или «причин для изменения». По сути, к чему стремится SRP, это то, что программные объекты (функции, классы и т. Д.) Должны выполнять одну задачу и делать это хорошо.
Если вы посмотрите на мое определение, оно не менее расплывчато, чем обычная формулировка SRP. Проблема с обычными определениями SRP заключается не в том, что они слишком расплывчаты, а в том, что они пытаются быть слишком конкретными в отношении чего-то, что по существу расплывчато.
Если вы посмотрите на то
calculatePay
, что делает, он явно делает одну вещь: отправка на основе типа. Поскольку в Java есть встроенные способы выполнения диспетчеризации на основе типов,calculatePay
она неэлегатична и не идиоматична, поэтому ее следует переписать, но не по указанным причинам.источник
Вы правы @Enrique. Независимо от того, является ли это функцией или методом класса, SRP означает, что в этом блоке кода вы делаете только одну вещь.
Утверждение «причина для изменения» иногда немного вводит в заблуждение, но если вы измените
calculateSalariedPay
илиcalculateHourlyPay
илиEmployee.type
ваше перечисление, вам придется изменить этот метод.В вашем примере функция:
По моему мнению, это не является прямым нарушением SRP, так как случаи переключения и вызовы не могут быть записаны короче, если вы думаете о Employee, и методы уже существуют. В любом случае, это явное нарушение принципа открытого-закрытого (OCP), так как вы должны добавлять операторы «case», если добавляете типы сотрудников, так что это плохая реализация: рефакторинг.
Мы не знаем, как рассчитывать «деньги», но самый простой способ - иметь
Employee
интерфейс и некоторые конкретные реализации сgetMoney
методами. В этом случае вся функция не нужна.Если его сложнее вычислить, можно использовать шаблон посетителя, который также не является 100% -ным SRP, но это больше OCP, чем случай коммутатора.
источник