Принцип единой ответственности гласит, что класс должен делать одно и только одно. Некоторые случаи довольно ясны. Другие, однако, сложны, потому что то, что выглядит как «одна вещь» при просмотре на данном уровне абстракции, может быть множественным при просмотре на более низком уровне. Я также опасаюсь, что если принцип «Единой ответственности» будет соблюдаться на более низких уровнях, то это может привести к чрезмерно развязанному, многословному коду равиоли, в котором тратится больше строк, создавая крошечные классы для всего и собирая информацию вокруг, чем фактически решая проблему.
Как бы вы описали, что означает «одна вещь»? Каковы конкретные признаки того, что класс действительно делает больше, чем «одно»?
design
object-oriented
dsimcha
источник
источник
Ответы:
Мне очень нравится, как Роберт К. Мартин (дядя Боб) формулирует принцип единой ответственности (ссылка на PDF) :
Оно слегка отличается от традиционного определения «должен делать только одно», и мне это нравится, потому что оно заставляет вас изменить свой взгляд на свой класс. Вместо того, чтобы думать о том, «это делает что-то одно?», Вы вместо этого думаете о том, что может измениться и как эти изменения влияют на ваш класс. Так, например, если база данных изменяется, должен ли ваш класс меняться? Что делать, если изменяется устройство вывода (например, экран, или мобильное устройство, или принтер)? Если ваш класс должен измениться из-за изменений во многих других направлениях, это признак того, что у вашего класса слишком много обязанностей.
В связанной статье дядя Боб делает вывод:
источник
Я продолжаю спрашивать себя, какую проблему пытается решить SRP? Когда мне помогает SRP? Вот что я придумал:
Вы должны рефакторинг ответственности / функциональности вне класса, когда:
1) Вы дублировали функционал (СУХОЙ)
2) Вы обнаружите, что вашему коду необходим другой уровень абстракции, чтобы помочь вам понять его (KISS)
3) Вы обнаружите, что специалисты по предметной области понимают, что части функциональности являются частью другого компонента (вездесущий язык)
Вы НЕ ДОЛЖНЫ рефакторинг ответственности вне класса, когда:
1) Нет дублирующихся функций.
2) Функциональность не имеет смысла вне контекста вашего класса. Другими словами, ваш класс предоставляет контекст, в котором легче понять функциональность.
3) Специалисты в вашей области не имеют понятия об этой ответственности.
Мне приходит в голову, что, если SRP применяется в широком смысле, мы обмениваемся одним видом сложности (пытаясь сделать головами или хвостами класса слишком много происходящего внутри) с другим видом (пытаясь сохранить всех коллабораторов / уровней прямая абстракция, чтобы понять, что на самом деле делают эти классы).
Если есть сомнения, оставьте это! Вы всегда можете выполнить рефакторинг позже, когда для этого есть четкое обоснование.
Как вы думаете?
источник
Я не знаю, существует ли для этого объективная шкала, но то, что от этого зависит, это методы - не столько их количество, сколько разнообразие их функций. Я согласен, что вы можете зайти слишком далеко, но я не буду следовать никаким жестким и быстрым правилам.
источник
Ответ заключается в определении
То, что вы определяете ответственность, в конечном итоге дает вам границу.
Пример:
У меня есть компонент, который отвечает за отображение счетов -> в этом случае, если я начал добавлять что-то еще, я нарушаю принцип.
Если, с другой стороны, если я сказал ответственность за обработку счетов-фактур -> добавление нескольких небольших функций (например, печать счета- фактуры , обновление счета- фактуры ), все они находятся в пределах этой границы.
Однако если бы этот модуль начал обрабатывать какую-либо функциональность вне счетов , то он был бы за этой границей.
источник
Я всегда смотрю на это на двух уровнях:
Итак, что-то вроде доменного объекта с именем Dog:
Dog
это мой класс, но собаки могут делать много вещей! У меня могут быть такие методы, какwalk()
,run()
иbite(DotNetDeveloper spawnOfBill)
(извините, не могу устоять; р).Если это
Dog
становится громоздким, то я бы подумал о том, как группы этих методов могут быть смоделированы вместе в другом классе, таком какMovement
класс, который может содержать моиwalk()
иrun()
методы.Там нет жесткого и быстрого правила, ваш дизайн ОО будет развиваться с течением времени. Я стараюсь использовать понятный интерфейс / публичный API, а также простые методы, которые хорошо выполняют одно и одно.
источник
Я смотрю на это больше по линии класса, должен представлять только одну вещь. Присвоить @ Например Karianna, я есть свой
Dog
класс, который имеет методыwalk()
,run()
иbark()
. Я не собираюсь добавлять методыmeaow()
,squeak()
,slither()
илиfly()
потому , что это не те вещи , которые делают собаки. Это вещи, которые делают другие животные, и у этих других животных были бы свои классы, чтобы представлять их.(Кстати, если ваша собака действительно летает, то вам, вероятно, следует прекратить выбрасывать ее из окна).
источник
SeesFood
как характеристикуDogEyes
,Bark
как нечто, совершаемое aDogVoice
, иEat
как нечто, совершаемое aDogMouth
, тоif (dog.SeesFood) dog.Eat(); else dog.Bark();
становится логически подобнымif (eyes.SeesFood) mouth.Eat(); else voice.Bark();
, утрачивая любое чувство идентичности, когда глаза, рот и голос связаны с одним правом.Dog
класса, то он, вероятно,Dog
связан. Если нет, то вы, вероятно, в конечном итоге что-то вроде,myDog.Eyes.SeesFood
а не простоeyes.SeesFood
. Другая возможность - этоDog
предоставлениеISee
интерфейса, который требуетDog.Eyes
свойства иSeesFood
метода.Eye
классом, но собака должна «видеть» используя свои глаза, а не просто глаза, которые могут видеть. Собака - это не глаз, но и не просто держатель глаз. Это «вещь, которую можно [хотя бы попытаться] увидеть», и она должна быть описана через интерфейс как таковая. Даже слепую собаку можно спросить, видит ли она еду; это не будет очень полезно, так как собака всегда скажет «нет», но спрашивать не вредно.Класс должен делать одну вещь, если рассматривать его на своем уровне абстракции. Это, несомненно, сделает много вещей на менее абстрактном уровне. Вот как классы работают, чтобы сделать программы более удобными в обслуживании: они скрывают детали реализации, если вам не нужно тщательно их изучать.
Я использую имена классов в качестве теста для этого. Если я не могу дать классу достаточно короткое описательное имя или если в этом имени есть слово типа «И», я, вероятно, нарушаю принцип единой ответственности.
По моему опыту, легче придерживаться этого принципа на более низких уровнях, где все более конкретно.
источник
Речь идет об одной уникальной роли .
Каждый класс должен быть возобновлен именем роли. На самом деле роль - это (набор) глаголов, связанных с контекстом.
Например :
Файл обеспечивает доступ к файлу. FileManager управляет объектами File.
Ресурс содержит данные для одного ресурса из файла. ResourceManager держать и предоставлять все ресурсы.
Здесь вы можете увидеть, что некоторые глаголы типа «управлять» подразумевают набор других глаголов. В большинстве случаев одни глаголы лучше воспринимать как функции, чем классы. Если глагол подразумевает слишком много действий, которые имеют свой общий контекст, то это должен быть класс сам по себе.
Итак, идея состоит лишь в том, чтобы дать вам простое представление о том, что делает класс, определив уникальную роль, которая может быть совокупностью нескольких под ролей (выполняемых объектами-членами или другими объектами).
Я часто строю классы Manager, в которых есть несколько других классов. Например, Фабрика, Реестр и т. Д. Посмотрите на класс Менеджера, как на своего рода руководителя группы, руководителя оркестра, который направляет другие народы работать вместе для достижения идеи высокого уровня. У него одна роль, но подразумевается работа с другими уникальными ролями внутри. Вы также можете увидеть, как организована компания: генеральный директор не является продуктивным на чистом уровне производительности, но если его там нет, то ничто не может работать правильно вместе. Это его роль.
Когда вы разрабатываете, определяйте уникальные роли. И для каждой роли, снова посмотрите, нельзя ли ее сократить в нескольких других ролях. Таким образом, если вам нужно просто изменить способ, которым ваш Менеджер строит объекты, просто измените Фабрику и действуйте спокойно.
источник
SRP - это не только разделение классов, но и делегирование функциональности.
В приведенном выше примере с собакой не используйте SRP в качестве оправдания, чтобы иметь 3 отдельных класса, таких как DogBarker, DogWalker и т. Д. (Низкая когезия). Вместо этого посмотрите на реализацию методов класса и решите, «знают ли они слишком много». У вас все еще может быть dog.walk (), но, вероятно, метод walk () должен делегировать другому классу детали того, как выполняется ходьба.
По сути, мы позволяем классу Dog иметь одну причину для изменения: потому что собаки меняются. Конечно, сочетая это с другими принципами SOLID, вы бы расширили Dog для новых функциональных возможностей, а не изменили Dog (открытый / закрытый). И вы бы внедрить ваши зависимости, такие как IMove и IEat. И, конечно, вы бы сделали эти отдельные интерфейсы (разделение интерфейса). Dog будет меняться только в том случае, если мы обнаружим ошибку или если Dogs кардинально изменится (Liskov Sub, не расширяйте, а затем удаляйте поведение).
Чистый эффект SOLID заключается в том, что мы пишем новый код чаще, чем нужно модифицировать существующий код, и это большая победа.
источник
Все зависит от определения ответственности и от того, как это определение повлияет на поддержку вашего кода. Все сводится к одной вещи, и именно так ваш дизайн поможет вам поддерживать ваш код.
И, как кто-то сказал: «Ходить по воде и разрабатывать программное обеспечение по заданным спецификациям легко, при условии, что они заморожены».
Итак, если мы определяем ответственность более конкретно, нам не нужно ее менять.
Иногда обязанности будут явно очевидны, но иногда они могут быть неуловимыми, и нам нужно принимать взвешенные решения.
Предположим, мы добавили еще одну ответственность в класс Dog с именем catchThief (). Теперь это может привести к дополнительной другой ответственности. Завтра, если полицейский отдел должен поменять способ, которым собака ловит вора, то класс собаки должен быть изменен. В этом случае было бы лучше создать другой подкласс и назвать его ThiefCathcerDog. Но, с другой стороны, если мы уверены, что он не изменится ни при каких обстоятельствах, или способ реализации catchThief зависит от какого-то внешнего параметра, тогда вполне нормально взять на себя эту ответственность. Если обязанности не являются поразительно странными, то мы должны решить их разумно на основе варианта использования.
источник
«Одна причина для изменения» зависит от того, кто использует систему. Убедитесь, что у вас есть варианты использования для каждого действующего лица, и составьте список наиболее вероятных изменений, для каждого возможного изменения варианта использования убедитесь, что это изменение затронет только один класс. Если вы добавляете совершенно новый вариант использования, убедитесь, что вам нужно только расширить класс для этого.
источник