Кажется довольно ясным, что «принцип единой ответственности» не означает «только одно». Вот для чего нужны методы.
public Interface CustomerCRUD
{
public void Create(Customer customer);
public Customer Read(int CustomerID);
public void Update(Customer customer);
public void Delete(int CustomerID);
}
Боб Мартин говорит, что «у классов должна быть только одна причина измениться». Но это сложно обдумать, если вы новичок в SOLID.
Я написал ответ на другой вопрос , где предположил, что обязанности похожи на должности, и танцевал вокруг предмета, используя метафору ресторана, чтобы проиллюстрировать мою точку зрения. Но это все еще не формулирует набор принципов, которые кто-то может использовать, чтобы определить обязанности своих классов.
Так как вы это делаете? Как вы определяете, какие обязанности должен иметь каждый класс, и как вы определяете ответственность в контексте ПСП?
architecture
class-design
solid
single-responsibility
Роберт Харви
источник
источник
Ответы:
Один из способов обдумать это - представить потенциальные изменения требований в будущих проектах и спросить себя, что вам нужно сделать, чтобы они произошли.
Например:
Или же:
Идея состоит в том, чтобы минимизировать воздействие будущих потенциальных изменений, ограничивая модификации кода одной областью кода на область изменения.
Как минимум, ваши занятия должны отделять логические проблемы от физических. Большой набор примеров можно найти в
System.IO
пространстве имен: там можно найти различные виды физических потоков (напримерFileStream
,MemoryStream
илиNetworkStream
) и различных читателей и писателей (BinaryWriter
,TextWriter
) , которые работают на логическом уровне. Разделяя их таким образом, мы избегаем комбинаторного взрыва: вместо того ,FileStreamTextWriter
,FileStreamBinaryWriter
,NetworkStreamTextWriter
,NetworkStreamBinaryWriter
,MemoryStreamTextWriter
, иMemoryStreamBinaryWriter
, вы просто подключить писатель и поток , и вы можете иметь то , что вы хотите. Затем мы можем добавить, скажем,XmlWriter
без необходимости повторной реализации для памяти, файлов и сети отдельно.источник
Практически говоря, обязанности ограничены теми вещами, которые могут измениться. Таким образом, нет научного или формального способа прийти к тому, что представляет собой ответственность, к сожалению. Это суждение.
Речь идет о том, что, по вашему опыту , может измениться.
Мы склонны применять язык принципа в гиперболической, буквальной, ревностной ярости. Мы склонны разделять классы, потому что они могут меняться, или по линиям, которые просто помогают нам разбить проблемы. (Последняя причина не плоха по своей сути .) Но SRP не существует ради самого себя; он служит для создания поддерживаемого программного обеспечения.
Опять же, если подразделения не руководствуются вероятными изменениями, они не будут действительно обслуживать SRP 1, если YAGNI более применим. Оба служат одной и той же конечной цели. И то, и другое - дело суждения - мы надеемся, что закаленный суд.
Когда дядя Боб пишет об этом, он предлагает нам подумать об «ответственности» с точки зрения «кто просит перемен». Другими словами, мы не хотим, чтобы Партия А потеряла свою работу, потому что Партия Б потребовала перемен.
Хорошие и опытные разработчики будут понимать, какие изменения вероятны. И этот ментальный список будет несколько отличаться в зависимости от отрасли и организации.
То, что представляет собой ответственность в вашем конкретном приложении, в вашей конкретной организации, в конечном счете, является предметом выдержанного суждения. Речь идет о том, что может измениться. И, в некотором смысле, речь идет о том, кто владеет внутренней логикой модуля.
1. Чтобы быть ясно, это не значит, что они плохие разделения. Это могут быть отличные подразделения, которые значительно улучшают читабельность кода. Это просто означает, что они не управляются SRP.
источник
Я следую "у классов должна быть только одна причина измениться".
Для меня это означает думать о мошеннических схемах, которые мог бы придумать мой владелец продукта («Нам нужно поддерживать мобильность!», «Нам нужно перейти в облако!», «Нам нужно поддерживать китайский!»). Хорошие проекты ограничат воздействие этих схем на меньшие площади и сделают их относительно простыми в реализации. Плохие разработки означают, что нужно много кода и вносить кучу рискованных изменений.
Опыт - единственное, что я нашел для правильной оценки вероятности этих сумасшедших схем - потому что упрощение одного может усложнить два других - и оценка качества дизайна. Опытные программисты могут представить, что им нужно сделать, чтобы изменить код, что лежит, чтобы укусить их в задницу, и какие хитрости делают вещи проще. Опытные программисты хорошо разбираются в том, насколько они запутаны, когда владелец продукта просит сумасшедших вещей.
Практически я считаю, что модульные тесты помогают здесь. Если ваш код негибкий, его будет сложно протестировать. Если вы не можете внедрить макеты или другие тестовые данные, вы, вероятно, не сможете внедрить этот
SupportChinese
код.Другая грубая метрика - высота лифта. Традиционные элеваторные передачи - «если вы были в лифте с инвестором, можете ли вы продать его по идее?». У стартапов должно быть простое, краткое описание того, что они делают - какова их цель. Аналогично, классы (и функции) должны иметь простое описание того, что они делают . Не «этот класс реализует некоторый fubar такой, что вы можете использовать его в этих конкретных сценариях». Что-то, что вы можете сказать другому разработчику: «Этот класс создает пользователей». Если вы не можете сообщить , что другим разработчикам, вы собираетесь получить ошибки.
источник
Никто не знает. Или, по крайней мере, мы не можем договориться об одном определении. Вот что делает SPR (и другие принципы SOLID) довольно спорным.
Я бы поспорил, что возможность выяснить, что является или не является обязанностью, является одним из навыков, которые разработчик программного обеспечения должен освоить в течение своей карьеры. Чем больше кода вы пишете и просматриваете, тем больше у вас опыта, чтобы определить, является ли что-то одним или несколькими обязанностями. Или если единая ответственность распределена между отдельными частями кода.
Я бы сказал, что основной целью SRP не является жесткое правило. Это напоминает нам о том, что нужно помнить о целостности кода и всегда прилагать определенные сознательные усилия для определения того, какой код является связным, а какой - нет.
источник
Я думаю, что термин «ответственность» полезен в качестве метафоры, поскольку он позволяет нам использовать программное обеспечение, чтобы исследовать, насколько хорошо оно организовано. В частности, я бы остановился на двух принципах:
Эти два принципа позволяют нам сознательно распределить ответственность, потому что они противоречат друг другу. Если вы даете возможность фрагменту кода сделать что-то для вас, он должен нести ответственность за то, что он делает. Это вызывает ответственность за то, что класс может расти, расширяя его «одну причину для изменения» на все более широкие области. Однако по мере того, как вы делаете вещи шире, вы, естественно, начинаете сталкиваться с ситуациями, когда несколько объектов отвечают за одно и то же. Это чревато проблемами в реальной ответственности, поэтому, конечно, это проблема и в кодировании. В результате этот принцип приводит к сужению областей действия, поскольку вы подразделяете ответственность на недублированные участки.
В дополнение к этим двум, третий принцип кажется разумным:
Рассмотрим свежеиспеченную программу ... с чистого листа. Сначала у вас есть только одна сущность, которая является программой в целом. Он несет ответственность за ... все. Естественно, в какой-то момент вы начнете делегировать ответственность функциям или классам. На этом этапе вступают в силу первые два правила, вынуждающие вас сбалансировать эту ответственность. Программа верхнего уровня по-прежнему отвечает за общий результат, точно так же, как менеджер отвечает за производительность своей команды, но каждому подразделению была делегирована ответственность, а вместе с ней и полномочия выполнять эту ответственность.
В качестве дополнительного бонуса это делает SOLID особенно совместимым с любой корпоративной разработкой программного обеспечения, которая может потребоваться. Каждая компания на планете имеет определенное представление о том, как делегировать ответственность, и они не все согласны. Если вы делегируете ответственность в своем программном обеспечении таким образом, который напоминает о собственном делегировании вашей компании, будущим разработчикам будет гораздо проще понять, как вы действуете в этой компании.
источник
На этой конференции в Йельском университете дядя Боб приводит такой забавный пример:
Он говорит, что
Employee
есть три причины для изменения, три источника требований к изменениям, и дает это юмористическое и насмешливое , но, тем не менее, иллюстративное объяснение:Он дает это решение, которое устраняет нарушение SRP, но все же должно устранить нарушение DIP, которое не показано на видео.
источник
Я думаю, что лучший способ подразделить вещи, чем «причины для изменения», это начать с размышлений о том, имеет ли смысл требовать, чтобы код, который должен выполнять два (или более) действия, должен был содержать отдельную ссылку на объект для каждого действия, и было бы полезно иметь открытый объект, который мог бы выполнять одно действие, но не другое.
Если ответы на оба вопроса положительные, это предполагает, что действия должны выполняться отдельными классами. Если ответов на оба вопроса нет, это предполагает, что с общественной точки зрения должен быть один класс; если код для этого будет громоздким, он может быть внутренне подразделен на частные классы. Если ответ на первый вопрос - «нет», а второй - «да», то для каждого действия должен быть отдельный класс плюс составной класс, который включает ссылки на экземпляры других.
Если у вас есть отдельные классы для клавиатуры кассового аппарата, бипера, числового считывания, принтера квитанций и кассового аппарата, и нет составного класса для полного кассового аппарата, то код, который должен обрабатывать транзакцию, может в итоге случайно вызвать в способ, который принимает ввод с клавиатуры одной машины, производит шум от звукового сигнала второй машины, показывает цифры на дисплее третьей машины, печатает квитанцию на принтере четвертой машины и выдает кассовый аппарат пятой машины. Каждая из этих подфункций может быть с пользой обработана отдельным классом, но также должен быть составной класс, который присоединяется к ним. Составной класс должен делегировать как можно больше логики составляющим классам,
Можно сказать, что «ответственность» каждого класса заключается либо в том, чтобы включить некоторую реальную логику, либо в обеспечение общей точки подключения для нескольких других классов, которые делают это, но важно прежде всего сосредоточиться на том, как клиентский код должен просматривать класс. Если для клиентского кода имеет смысл видеть что-то как один объект, то клиентский код должен видеть это как один объект.
источник
SRP трудно понять правильно. Это в основном вопрос назначения «заданий» вашему коду и обеспечения четкой ответственности каждой части. Как и в реальной жизни, в некоторых случаях разделение работы между людьми может быть вполне естественным, но в других случаях это может быть очень сложно, особенно если вы не знаете их (или работу).
Я всегда рекомендую вам сначала написать простой код, который сначала работает , а затем немного поменять местами: через некоторое время вы, как правило, увидите, как кластеризуется код. Я считаю ошибкой навязывать обязанности, прежде чем вы узнаете код (или людей) и работу, которую предстоит сделать.
Одна вещь, которую вы заметите, это когда модуль начинает делать слишком много и его трудно отлаживать / поддерживать. Это момент для рефакторинга; какой должна быть основная работа и какие задачи можно поручить другому модулю? Например, должен ли он обрабатывать проверки безопасности и другую работу, или вы должны сначала выполнить проверки безопасности в другом месте, или это сделает код более сложным?
Используйте слишком много косвенных указаний, и это снова станет беспорядком ... что касается других принципов, этот будет конфликтовать с другими, такими как KISS, YAGNI и т. Д. Все зависит от баланса.
источник
«Принцип единой ответственности», возможно, вводит в заблуждение. «Только одна причина для изменения» - лучшее описание принципа, но его легко понять неправильно. Мы не говорим о том, что заставляет объекты изменять состояние во время выполнения. Мы думаем о том, что может заставить разработчиков в будущем менять код.
Если мы не исправим ошибку, изменение будет связано с новым или измененным бизнес-требованием. Вам придется думать вне самого кода и представить, какие внешние факторы могут привести к тому, что требования изменятся независимо . Сказать:
В идеале вы хотите, чтобы независимые факторы влияли на разные классы. Например, поскольку налоговые ставки изменяются независимо от названий продуктов, изменения не должны затрагивать одни и те же классы. В противном случае вы рискуете изменить налоговую декларацию, что приведет к ошибке в наименовании продукта, что является типом тесной связи, которую вы хотите избежать с помощью модульной системы.
Так что не просто сосредоточьтесь на том, что может измениться - все может измениться в будущем. Сосредоточьтесь на том, что может измениться независимо . Изменения обычно независимы, если они вызваны разными участниками.
Ваш пример с названиями должностей находится на правильном пути, но вы должны понимать это буквально! Если маркетинг может вызвать изменения в коде, а финансы могут вызвать другие изменения, эти изменения не должны влиять на один и тот же код, поскольку это буквально разные названия должностей и, следовательно, изменения будут происходить независимо.
Цитирую дядю Боба, который придумал этот термин:
Итак, подведем итог: «ответственность» обслуживает одну бизнес-функцию. Если более чем один актер может заставить вас изменить класс, то этот класс, вероятно, нарушает этот принцип.
источник
Хорошая статья, которая объясняет принципы программирования SOLID и дает примеры кода, которые следуют и не следуют этим принципам, - https://scotch.io/bar-talk/solid-the-first-five-principles-of-object-oriented- дизайн .
В примере, касающемся SRP, он приводит пример нескольких классов фигур (круг и квадрат) и класс, предназначенный для расчета общей площади нескольких фигур.
В своем первом примере он создает класс вычисления площади и возвращает его в виде HTML. Позже он решает, что вместо этого он хочет отобразить его как JSON и должен изменить свой класс вычисления площади.
Проблема с этим примером состоит в том, что его класс вычисления площади отвечает за вычисление области фигур И отображение этой области. Затем он предлагает лучший способ сделать это, используя другой класс, разработанный специально для отображения областей.
Это простой пример (и его легче понять при чтении статьи, поскольку он содержит фрагменты кода), но он демонстрирует основную идею SRP.
источник
Прежде всего, то, что у вас есть, на самом деле - две отдельные проблемы: проблема того, какие методы поместить в ваши классы, и проблема раздувания интерфейса.
Интерфейсы
У вас есть этот интерфейс:
Предположительно, у вас есть несколько классов, которые соответствуют
CustomerCRUD
интерфейсу (в противном случае интерфейс не требуется), и некоторая функция,do_crud(customer: CustomerCRUD)
которая принимает соответствующий объект. Но вы уже нарушили SRP: вы связали эти четыре различные операции вместе.Допустим, позже вы будете работать с представлениями базы данных. Представление базы данных имеет только на
Read
доступный метод для этого. Но вы хотите написать функцию,do_query_stuff(customer: ???)
которая прозрачно оперирует с полнофункциональными таблицами или представлениями; вRead
конце концов, он использует только метод.Так что создайте интерфейс
public Interface CustomerReader {общедоступное чтение клиента (customerID: int)}
и фактор вашего
CustomerCrud
интерфейса как:Но конца не видно. Могут быть объекты, которые мы можем создавать, но не обновлять и т. Д. Эта кроличья нора слишком глубокая. Единственный разумный способ придерживаться принципа единой ответственности - сделать так, чтобы все ваши интерфейсы содержали ровно один метод . Go фактически следует этой методологии из того, что я видел, с подавляющим большинством интерфейсов, содержащих одну функцию; Если вы хотите указать интерфейс, который содержит две функции, вы должны неловко создать новый интерфейс, который объединяет эти две функции. Вы скоро получите комбинаторный взрыв интерфейсов.
Выходом из этого беспорядка является использование структурных подтипов (реализованных, например, в OCaml) вместо интерфейсов (которые являются формой номинального подтипирования). Мы не определяем интерфейсы; вместо этого мы можем просто написать функцию
это вызывает любые методы, которые нам нравятся. OCaml будет использовать вывод типа, чтобы определить, что мы можем передать любой объект, который реализует эти методы. В этом примере было бы определить, что
customer
имеет тип<read: int -> unit, update: int -> unit, ...>
.Классы
Это решает интерфейсный беспорядок; но мы все еще должны реализовать классы, которые содержат несколько методов. Например, должны ли мы создать два разных класса,
CustomerReader
аCustomerWriter
? Что если мы захотим изменить способ чтения таблиц (например, теперь мы кэшируем наши ответы в redis до получения данных), но теперь как они пишутся? Если вы будете следовать этой цепочке рассуждений до логического завершения, вы будете неразрывно связаны с функциональным программированием :)источник
На мой взгляд, самая близкая вещь к SRP, которая приходит мне в голову, это поток использования. Если у вас нет четкого потока использования для какого-либо данного класса, вероятно, ваш класс имеет запах дизайна.
Поток использования - это заданная последовательность вызовов методов, которая даст ожидаемый (таким образом проверяемый) результат. Вы в основном определяете класс с использованием сценариев использования, которые он получил IMHO, поэтому вся методология программы фокусируется на интерфейсах, а не на реализации.
источник
Это для достижения того, чтобы множественные изменения требований не требовали изменения вашего компонента .
Но удачи, понимая, что на первый взгляд, когда вы впервые услышите о SOLID.
Я вижу много комментариев, в которых говорится, что SRP и YAGNI могут противоречить друг другу, но YAGN, который я соблюдаю при помощи TDD (GOOS, London School), научил меня думать и разрабатывать свои компоненты с точки зрения клиента. Я начал проектировать свои интерфейсы по наименьшему из пожеланий любого клиента, вот как мало он должен делать . И это упражнение может быть сделано без каких-либо знаний TDD.
Мне нравится техника, описанная дядей Бобом (к сожалению, я не могу вспомнить откуда), которая выглядит примерно так:
Этот метод является абсолютным, и, как сказал @svidgen, SRP - это суждение, но когда вы узнаете что- то новое, абсолюты являются лучшими, легче просто всегда что-то делать. Убедитесь, что причина, по которой вы не отделяетесь, есть; образованная оценка, и не потому, что вы не знаете, как. Это искусство, и оно требует опыта.
Я думаю, что многие ответы приводят аргумент в пользу разъединения, когда речь идет о SRP .
SRP это не чтобы убедиться , что изменение не распространяется вниз граф зависимостей.
Теоретически, без SRP у вас не будет никаких зависимостей ...
Одно изменение не должно вызывать изменения во многих местах приложения, но у нас есть для этого другие принципы. SRP , однако, улучшает открытый закрытый принцип . Этот принцип больше относится к абстракции, однако меньшие абстракции легче переопределить .
Поэтому, обучая SOLID в целом, будьте осторожны и учите, что SRP позволяет изменять меньше кода при изменении требований, а на самом деле позволяет писать меньше нового кода.
источник
When learning something new, absolutes are the best, it is easier to just always do something.
- По моему опыту, новые программисты слишком догматичны. Абсолютизм ведет к не думающим разработчикам и к культу культового программирования. Сказать «просто сделай это» - это хорошо, если ты понимаешь, что человеку, с которым ты разговариваешь, придется позже отучиться от того, чему ты их научил.На это нет однозначного ответа. Хотя вопрос узкий, объяснения нет.
Для меня это что-то вроде бритвы Оккама, если хотите. Это идеал, в котором я пытаюсь измерить свой текущий код. Трудно прибить это простыми и понятными словами. Другой метафорой будет «одна тема», которая столь же абстрактна, то есть трудна для понимания, как «единая ответственность». Третий дескрипт будет «иметь дело с одним уровнем абстракции».
Что это значит практически?
В последнее время я использую стиль кодирования, который состоит в основном из двух фаз:
Фаза I лучше всего описана как творческий хаос. На этом этапе я записываю код, потому что мысли текут - то есть сыры и безобразны.
Фаза II - полная противоположность. Это похоже на уборку после урагана. Это требует наибольшей работы и дисциплины. А потом я смотрю на код с точки зрения дизайнера.
Сейчас я работаю в основном на Python, что позволяет мне думать об объектах и классах позже. Первая фаза I - я пишу только функции и выкладываю их почти случайно в разных модулях. На втором этапе , после того, как я приступил к работе, я более подробно рассмотрю, какой модуль имеет дело с какой частью решения. И пока я просматриваю модули, темы меняются. Некоторые функции тематически связаны. Это хорошие кандидаты на занятия . И после того, как я превратил функции в классы - что почти сделано с отступом и добавлением
self
в список параметров в python;) - я используюSRP
как Razor Оккама, чтобы выделить функциональность для других модулей и классов.Текущий пример может писать небольшую функциональность экспорта на днях.
Была необходимость в CSV , Excel и комбинированных листах Excel в формате ZIP.
Простая функциональность была выполнена в трех видах (= функции). Каждая функция использовала общий метод определения фильтров и второй метод для извлечения данных. Затем в каждой функции выполнялась подготовка экспорта, которая доставлялась в виде ответа от сервера.
Было перепутано слишком много уровней абстракции:
I) работа с входящим / исходящим запросом / ответом
II) определение фильтров
III) получение данных
IV) преобразование данных
Легким шагом было использование одной абстракции (
exporter
) для работы со слоями II-IV на первом этапе.Осталась только тема, касающаяся запросов / ответов . На том же уровне абстракции извлекает параметры запроса, что нормально. Так что я имел за это мнение одну «ответственность».
Во-вторых, мне пришлось разбить экспортер, который, как мы видели, состоял как минимум из трех других уровней абстракции.
Определение критериев фильтра и фактическое извлечение почти на одном уровне абстракции (фильтры необходимы для получения правильного подмножества данных). Эти уровни были помещены в нечто вроде уровня доступа к данным .
На следующем шаге я разделил действительные механизмы экспорта: там, где требовалась запись во временный файл, я разбил это на две «обязанности»: одну для фактической записи данных на диск и другую часть, которая касалась фактического формата.
По мере формирования классов и модулей все стало яснее, что и где принадлежало. И всегда скрытый вопрос, делает ли класс слишком много .
Трудно дать рецепт для подражания. Конечно, я мог бы повторить загадочный «один уровень абстракции» - правило, если это помогает.
В основном для меня это своего рода «художественная интуиция», которая ведет к текущему дизайну; Я моделирую код, как художник, который лепит глину или рисует.
Представь меня кодирующим Бобом Россом ;)
источник
Что я пытаюсь сделать, чтобы написать код, который следует SRP:
Пример:
Проблема: получить от пользователя два числа, вычислить их сумму и вывести результат пользователю:
Затем попытайтесь определить обязанности на основе задач, которые необходимо выполнить. Из этого извлеките соответствующие классы:
Затем переработанная программа становится:
Примечание: этот очень простой пример учитывает только принцип SRP. Использование других принципов (например, «L» -код должен зависеть от абстракций, а не от конкреций) обеспечит больше преимуществ для кода и сделает его более понятным для бизнес-изменений.
источник
Из книги Роберта К. Мартинса « Чистая архитектура: руководство мастера по структуре и дизайну программного обеспечения» , опубликованной 10 сентября 2017 года, Роберт пишет на странице 62 следующее:
Так что это не о коде. SRP - это контроль потока требований и потребностей бизнеса, которые могут поступать только из одного источника.
источник