Как определить, соответствует ли класс единому принципу ответственности?

34

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

Но как нам определить, обладает ли определенный класс набором обязанностей и, следовательно, является ли он очень сплоченным, или же он несет только одну ответственность и, следовательно, придерживается ПСП? Другими словами, не является ли это более или менее субъективным, поскольку некоторые могут считать класс очень гранулированным (и, как таковые, будут считать, что класс придерживается SRP), в то время как другие могут считать его недостаточно детализированным?

user1483278
источник

Ответы:

21

Почему да, это очень субъективно, и это предмет многих горячих, краснолицых дебатов, в которые попадают программисты.

На самом деле нет единственного ответа, и ответ может измениться, поскольку ваше программное обеспечение становится более сложным. То, что когда-то было единственной четко определенной задачей, может в конечном итоге стать множеством плохо определенных задач. Это всегда загвоздка. Как вы выбираете правильный способ разделить программу на задачи?

О единственном совете, который я могу дать, это: используйте ваше (и ваших коллег) лучшее суждение. И помните, что ошибки (как правило) могут быть исправлены, если вы поймете их достаточно скоро.

Джейсон Бейкер
источник
Я хотел бы, чтобы информатика была больше похожа на реальную науку. Субъективности нет места в реальной науке. Хотя принципы SOLID хороши сами по себе, их необходимо повторять, чтобы минимизировать субъективность и максимально объективность. Этого еще не произошло, что заставляет меня усомниться в их легитимности в реальном мире.
DarkNeuron
13

Боб Мартин (дядя Боб), который создал принципы SOLID, первым из которых является SRP , говорит об этом (я перефразирую, не могу вспомнить реальные слова):

У класса должна быть только одна причина для изменения

Если у этого есть больше чем одна причина, это не придерживается SRP.

Одед
источник
14
Это просто повторяет определение, но на самом деле соблюдение srp все еще довольно субъективно.
Энди
7

Я могу дать вам несколько практических правил.

  • Насколько легко назвать класс? Если класс трудно назвать, он, вероятно, делает слишком много.
  • Сколько открытых методов у класса? 7 +/- 2 - хорошее эмпирическое правило. Если у класса есть нечто большее, вам следует подумать о том, чтобы разделить его на несколько классов.
  • Существуют ли сплоченные группы открытых методов, используемых в разных контекстах?
  • Сколько существует частных методов или членов данных? Если класс имеет сложную внутреннюю структуру, вам, вероятно, следует реорганизовать его, чтобы внутренние компоненты были упакованы в отдельные меньшие классы.
  • И самое простое правило: насколько велик класс? Если у вас есть заголовочный файл C ++, содержащий один класс длиной более двух сотен строк, вам, вероятно, следует разделить его.
Дима
источник
2
Что касается вашего второго пункта, см. Uxmyths.com/post/931925744/…
Кэмерон Мартин
7
Сильно не согласен по поводу 7 +/- 2 - принцип единой ответственности касается семантической сплоченности, а не произвольных чисел.
JacquesB
1
Правило большого пальца не нуждается в независимых научных доказательствах. Современному научному методу испокон веков, архитектуре и технике - тысячелетия. Практическое правило для открытых методов - «несколько», а ни один из параметров - «несколько». В других новостях, даже если на некоторых детских рисунках видно, что в противном случае руки людей не выходят из головы.
abuzittin gillifirca
@CameronMartin В зависимости от ваших настроек, интерфейс для класса может или не может быть легко доступен для чтения. Поиск пользовательского интерфейса - это не то же самое, что написание кода - если мне приходится каждую минуту просматривать документацию, я, по крайней мере, удваиваю время, необходимое для выполнения какой-либо реальной работы.
уточнение
6

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

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

Далее он объяснил концепцию на примере ЗДЕСЬ .

Тед
источник
Это отличная статья, написанная самим человеком.
MrDustpan
4

Чтобы ответить на это, сделайте шаг назад и рассмотрите намерение принципа единой ответственности. Почему это рекомендуемый принцип дизайна в первую очередь?

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

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

Так в чем же тогда «ответственность»? Это то, что может измениться независимо от других изменений. Допустим, у вас есть программа, которая может сохранить некоторые настройки в файл конфигурации XML и прочитать их обратно из файла. Это одна ответственность, или «загрузить» и «сохранить» две разные обязанности? Любое изменение формата или структуры файла потребует изменения как логики загрузки, так и сохранения. Поэтому это единственная ответственность, которая должна быть представлена ​​одним классом. Теперь рассмотрим приложение, которое может экспортировать некоторые данные в формат CVS, Excel и XML. В этом случае легко представить, что один формат может измениться, не влияя на другой. Если вы решите изменить разделитель в формате CVS, это не должно повлиять на вывод в Excel.

JacquesB
источник
2

ОО говорит, что классы - это группировка данных и функциональность. Это определение оставляет много места для субъективной интерпретации.

Мы знаем, что классы должны быть четко и легко определены. Но чтобы определить такой класс, мы должны иметь четкое представление о том, как класс вписывается в общий дизайн. Без требований типа водопада, которые, как это ни парадоксально, считаются антишаблоном ... этого трудно достичь.

Мы можем реализовать дизайн класса с архитектурой, которая работает в большинстве случаев, например, MVC. В приложениях MVC мы предполагаем, что у нас есть только данные, пользовательский интерфейс и требование к ним для общения.

С базовой архитектурой легче выявлять случаи нарушения единых правил ответственности. EG Передача экземпляра пользовательского элемента управления модальному.

P.Brian.Mackey
источник
1

Просто для обсуждения я приведу класс от JUCE под названием AudioSampleBuffer . Теперь этот класс существует для хранения фрагмента (или, возможно, довольно длинного фрагмента) аудио. Он знает, что число каналов, количество выборок (на канал), кажется, соответствует 32-разрядному IEEE-плавающему, вместо того, чтобы иметь переменное числовое представление или размер слова (но это не проблема для меня). Существуют функции-члены, которые позволяют вам получать numChannels или numSamples и указатели на любой конкретный канал. Вы можете сделать AudioSampleBuffer длиннее или короче. Я предполагаю, что первые нулевые буферы буферизируют, а последние усекают.

Есть несколько закрытых членов этого класса, которые используются для выделения пространства в специальной куче, которую использует JUCE.

Но это то, чего не хватает в AudioSampleBuffer (и я несколько раз обсуждал это с Жюлем): член вызвал SampleRate. Как это могло пропустить это?

Единственная ответственность, которую AudioSampleBuffer должен выполнить, состоит в том, чтобы адекватно представлять физическое аудио, которое слышит тот, который представляют его образцы. Когда вы вводите AudioSampleBuffer из чего-то, что читает звуковой файл или из потока, есть дополнительный параметр, который вы должны получить и передать его вместе с AudioSampleBuffer в методы обработки (скажем, это фильтр), которые должны знать частоту дискретизации или, в конце концов, к методу, который воспроизводит буфер для прослушивания (или передает его в другое место). Без разницы.

Но то, что вам нужно сделать, это продолжать передавать этот SampleRate, который присущ конкретному аудио, живущему в AudioSampleBuffer, везде и всюду. Я видел код, в котором константе 44100.0f была передана функция, потому что программист, похоже, не знал, что еще делать.

Это пример невыполнения единой обязанности.

Роберт Бристоу-Джонсон
источник
1

Исходя из того, что вы сказали, можно сделать конкретный путь - высокая сплоченность ведет к единственной ответственности, которую вы можете измерить. Максимальный связный класс имеет все поля, используемые во всех методах. Хотя максимальный связный класс не всегда возможен и не желателен, все же лучше достичь этого. Имея эту цель разработки класса, довольно легко сделать вывод, что в вашем классе не может быть много методов или полей (некоторые говорят, что самое большее 7).

Другой способ - из чистых основ ООП - модели после реальных объектов. Намного легче увидеть ответственность реальных объектов. Однако, если реальный объект слишком сложен, разбейте его на несколько контактирующих объектов, каждый из которых несет свою ответственность.

m3th0dman
источник