что такое сингл респонсибилити

Single Responsibility Principle. Не такой простой, как кажется

что такое сингл респонсибилити. Смотреть фото что такое сингл респонсибилити. Смотреть картинку что такое сингл респонсибилити. Картинка про что такое сингл респонсибилити. Фото что такое сингл респонсибилитиSingle responsibility principle, он же принцип единой ответственности,
он же принцип единой изменчивости — крайне скользкий для понимания парень и столь нервозный вопрос на собеседовании программиста.

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

В лесу нас разделили на группы по 8-9 человек в каждой и устроили соревнование — какая группа быстрее выпьет бутылку водки при условии, что первый человек из группы наливает водку в стакан, второй выпивает, а третий закусывает. Выполнивший свою операцию юнит встает в конец очереди группы.

Случай, когда размер очереди был кратен трем, и являлся хорошей реализацией SRP.

Определение 1. Единая ответственность.

Официальное определение принципа единой ответственности (SRP) говорит о том, что у каждого объекта есть своя ответственность и причина существования и эта ответственность у него только одна.

Рассмотрим объект «Выпивоха» (Tippler).
Для выполнения принципа SRP разделим обязанности на троих:

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

Выпивоха же, в свою очередь является фасадом для данных операций:

что такое сингл респонсибилити. Смотреть фото что такое сингл респонсибилити. Смотреть картинку что такое сингл респонсибилити. Картинка про что такое сингл респонсибилити. Фото что такое сингл респонсибилити

Зачем?

Человек-программист пишет код для человека-обезьяны, а человек-обезьяна невнимателен, глуп и вечно куда-то спешит. Он может удержать и понять около 3 — 7 термов в один момент времени.
В случае выпивохи этих термов три. Однако если мы напишем код одной простыней, то в нем появятся руки, стаканы, мордобои и бесконечные споры о политике. И все это будет в теле одного метода. Уверен — вы видели такой код в своей практике. Не самое гуманное испытание для психики.

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

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

Так вот, SRP — это принцип, объясняющий КАК декомпозировать, то есть где провести линию разделения.

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

что такое сингл респонсибилити. Смотреть фото что такое сингл респонсибилити. Смотреть картинку что такое сингл респонсибилити. Картинка про что такое сингл респонсибилити. Фото что такое сингл респонсибилити

Вернемся к выпивохе и плюсам, которые получает человек-обезьянка при декомпозировании:

(Ой, кажется это уже OCP принцип, и я нарушил ответственность этого поста)

И, конечно же, минусы:

Определение 2. Единая изменчивость.

Позвольте господа! Класс выпивохи же также выполняет единую ответственность — он выпивает! И вообще, слово «ответственность» — понятие крайне размытое. Кто-то ответственен за судьбу человечества, а кто-то ответственен за поднимание опрокинутых на полюсе пингвинов.

Рассмотрим две реализации выпивохи. Первая, указанная выше, содержит в себе три класса — налить, выпить и закусить.

Вторая, написана через методологию «Вперед и только вперед» и содержит всю логику в методе Act:

Оба этих класса, с точки зрения стороннего наблюдателя, выглядят абсолютно одинаково и выполняют единую ответственность «выпить».

Тогда мы лезем в интернет и узнаем другое определение SRP — Принцип единой изменчивости (Single Changeability Principle).

SCP гласит, что «У модуля есть один и только один повод для изменения«. То есть «Ответственность — это повод для изменения».

(Похоже, ребята, придумавшие изначальное определение были уверены в телепатических способностях человека-обезьяны)

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

Определение 3. Локализация изменений.

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

Начнем логировку с процесса наливания:

Инкапсулировав ее в PourOperation, мы поступили мудро с точки зрения ответственности и инкапсуляции, но вот с принципом изменчивости у нас теперь конфуз. Помимо самой операции, которая может меняться, изменчивой становится и сама логировка. Придется разделять и делать специальный логировщик для операции наливания:

Дотошный читатель заметит, что LogAfter, LogBefore и OnError также могут меняться по отдельности, и по аналогии с предыдущими действиями создаст три класса: PourLoggerBefore, PourLoggerAfter и PourErrorLogger.

А вспомнив, что операций для выпивохи три — получаем девять классов логирования. В итоге весь выпивоха состоит из 14 (. ) классов.

Гипербола? Едва ли! Человек-обезьянка с декомпозиционной гранатой раздробит “наливателя” на графин, стакан, операторы наливания, сервис подачи воды, физическую модель столкновения молекул и следующий квартал будет пытаться распутать зависимости без глобальных переменных. И поверьте — он не остановится.

Именно на этом моменте многие приходят к выводу, что SRP — это сказки из розовых королевств, и уходят вить лапшу.

… так и не узнав о существовании третьего определения Srp:

«Принцип единой ответственности гласит, что схожие для изменения вещи должны храниться в одном месте«. или “То, что изменяется вместе, должно храниться в одном месте

То есть, если мы меняем логировку операции, то мы должны это менять в одном месте.

Это очень важный момент — так как все объяснения SRP, которые были выше, говорили о том, что надо дробить типы, пока они дробятся, то есть накладывало «ограничение сверху» на размер объекта, а теперь мы говорим уже и об «ограничении снизу». Иными словами, SRP не только требует «дробить пока дробится», но и не перестараться — «не раздробить сцепленные вещи». Это великая битва бритвы Оккама с человеком-обезьяной!

что такое сингл респонсибилити. Смотреть фото что такое сингл респонсибилити. Смотреть картинку что такое сингл респонсибилити. Картинка про что такое сингл респонсибилити. Фото что такое сингл респонсибилити

Теперь выпивохе должно стать полегче. Помимо того, что не надо дробить логировщик IPourLogger на три класса, мы также можем объединить все логировщики в один тип:

И если нам добавится четвертый тип операции, то для нее уже готова логировка. А код самих операций чист и избавлен от инфраструктурного шума.

В результате у нас 5 классов для решения задачи выпивания:

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

И было в этом списке еще около 10-ти бизнес операций с жуткой связанностью. Объект счета нужен был почти всем. Идентификатор точки и имя клиента нужны были в половине вызовов.

После часового рефакторинга, мы смогли отделить инфраструктурный код и некоторые нюансы работы с аккаунтом в отдельные методы/классы. God метод полегчал, но осталось 100 строк кода, которые распутываться никак не хотели.

Лишь через несколько дней пришло понимание, что суть этого «полегчавшего» метода — и есть бизнес алгоритм. И что изначальное описание ТЗ было довольно сложным. И именно попытка разбить на куски этот метод будет нарушением SRP, а не наоборот.

Формализм.

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

Формализм 1. Определение SRP

Формализм 2. Необходимые критерии самопроверки.

Мне не встречались достаточные критерии выполнения SRP. Но есть необходимые условия:

1) Задайте себе вопрос — что делает этот класс/метод/модуль/сервис. вы должны ответить на него простым определением. ( благодарю Brightori )

Впрочем иногда подобрать простое определение очень сложно

2) Фикс некоторого бага или добавление новой фичи затрагивает минимальное количество файлов/классов. В идеале — один.

Так как ответственность (за фичу или баг) инкапсулированна в одном файле/классе, то вы точно знаете где искать и что править. Например: фича изменения вывода логировки операций потребует изменить только логировщик. Бегать по всему остальному коду не требуется.

Другой пример — добавление нового UI-контрола, схожего с предыдущими. Если это заставляет вас добавить 10 разных сущностей и 15 разных конвертеров — кажется, вы “передробили”.

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

4) При уточняющем вопросе про бизнес логику (от разработчика или менеджера) вы лезете строго в один класс/файл и получаете информацию только от туда.

Фичи, правила или алгоритмы компактно написаны каждая в одном месте, а не разбросаны флагами по всему пространству кода.

Наш класс или метод ответственен за что-то одно, и ответственность отражена в его названии

AllManagersManagerService — скорее всего, God-класс
LocalPayment — вероятно, нет

Формализм 3. Методика разработки «Оккама-first».

В начале проектирования, человек-обезьянка не знает и не чувствует всех тонкостей решаемой задачи и может дать маху. Ошибаться можно по разному:

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

Пора закругляться

Сфера применения SRP не ограничивается ООП и SOLID. Он применим к методам, функциям, классам, модулям, микросервисам и сервисам. Он применим как к “фигакс-фигакс-и-в-прод”, так и к “рокет-сайнс” разработке, везде делая мир чуточку лучше. Если задуматься, то это едва ли не фундаментальный принцип всей инженерии. Машиностроение, системы управления, да и вообще все сложные системы — строятся из компонентов, и “недодробление” лишает конструкторов гибкости, “передробление” — эффективности, а неверные границы — разума и душевного спокойствия.

что такое сингл респонсибилити. Смотреть фото что такое сингл респонсибилити. Смотреть картинку что такое сингл респонсибилити. Картинка про что такое сингл респонсибилити. Фото что такое сингл респонсибилити

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

Источник

Принцип единственной ответственности. Single Responsibility Principle

В этом уроке мы поговорим о первом принципе из пяти, который называется «Принцип единственной ответственности».

В этом уроке мы поговорим о первом принципе из пяти, который называется «Принцип единственной ответственности». Также его называют «Принцип единственной обязанности». В английском варианте он называется «Single Responsibility Principle».

Как можно понимать это правило? Что значит только одна причина для изменения? Чтобы определить причину для изменения класса, для начала нужно определить, какие у класса обязанности, то есть какие функции этот класс выполняет.

Давайте посмотрим на класс Service. Это публичный статический класс, который выполняет следующие функции: вернуть изображение, сохранить изображение, отправить email-сообщение, сгенерировать новый XML-файл sitemap.xml. То есть это какой-то служебный класс в каком-нибудь приложении. Такие классы создаются довольно часто.

Давайте попробуем определить, сколько у этого класса обязанностей. Мы видим, что этот класс умеет работать с изображениями, умеет работать с email и умеет работать с XML-файлами. Конечно же, список методов в этом классе немного упрощен. Вполне может быть, что здесь будут и другие методы, например, не только создавать xml-файл, но также методы для изменения этого xml-файла. То же самое и с email и с изображениями.

Итак, получается, что у этого класса целых три обязанности. Он должен работать с изображениями, должен работать с почтой и должен работать с файлом sitemap.xml.

Каждая из обязанностей класса – это потенциальная причина для изменения этого класса. В классе Service три обязанности, и, соответственно, три причины для изменения этого класса.

Чем это может быть опасно? Такой класс со множеством обязанностей будет очень часто изменяться в процессе жизни приложения. В больших сложных приложениях требования меняются очень часто. Например, заказчик потребует ввести новый функционал. Как результат, класс Service будет меняться чаще других, потому что он взял на себя очень много обязанностей. Например, нам нужно изменить работу с изображениями – обращаемся к классу Service, нам нужно изменить работу с email-сообщениями – обращаемся к классу Service. И так далее. Чем больше у класса обязанностей, тем больше вероятность, что класс будет изменяться. А это очень плохо.

В дизайне нашего приложения появляются такие признаки, как жесткость и хрупкость, вспомните материал прошлого урока. Соответственно, возрастает вероятность появления ошибок в самых неожиданных местах при изменениях кода. Более того, такой код становится труднее изменять. Также затрудняется тестирование этого класса, и тестирования кода, в котором используется этот класс. Также вполне может быть ситуация, что этот класс используется не только внутри одного приложения, но и многими другими приложениями внутри компании. Любое изменение в этом классе Service ставит под вопрос безопасность его использования как в текущем, так и в других приложениях, которые его используют.

В общем, последствия могут быть самыми тяжелыми.

Нам нужно спроектировать класс так, чтобы он как можно реже изменялся. Это подтверждает и правило принципа единственной ответственности – у класса должна быть только одна причина для изменения.

Чтобы этого добиться, нам нужно распределить обязанности, которые сейчас лежат на классе Service, по нескольким разным классам. То есть, чтобы у каждого класса была только одна обязанность.

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

Мы проанализировали класс Service и определили, что у него три обязанности. Давайте разделим эти обязанности по разным классам. Вот что у нас получится.

Все, что относится к работе с изображениями мы вынесли в отдельный класс ImageService. Ответственность этого класса – работа с изображениями.

Таким же образом поступим с почтой и XML-файлами. Класс EmailService отвечает за работу с почтой, а класс SiteMapFileService отвечает за работу с этим xml-файлом.

Итак, раньше у нас был класс Service, который умел абсолютно все, начиная от работы с почтой и заканчивая рисованием изображений. То есть он был наделен множеством обязанностей. Чтобы реализовать принцип единственной ответственности мы разделили класс Service на несколько более специализированных классов. У каждого такого класса теперь одна своя обязанность. Какой-то класс отвечает за работу с почтой, какой-то класс отвечает за работу с изображениями, и так далее.

Какие плюсы мы получили от такого разделения?

Во-первых, новые классы стали более легкими и понятными. В них содержится меньше кода, и вообще не составляет труда определить, что тот или иной класс делает. Сразу понятно, что класс EmailService ответственен за работу с почтой, SiteMapFileService ответственен за работу с файлом sitemap.xml и так далее.

Во-вторых, самое главное, мы реализовали принцип единственной обязанности. Теперь у нас каждый класс имеет одну обязанность, и, соответственно, только одну причину для изменения. Если нам, например, нужно будет изменить логику работы с почтой, то мы будем обращаться и изменять класс EmailService, в то время как два других класса, ImageService и SiteMapFileService останутся нетронутыми. А значит, мы будем уверены, что они по-прежнему работают правильно. Нам нужно будет протестировать только работу с классом EmailService.

Давайте рассмотрим еще один пример – класс Student. Этот класс описывает сущность «студент». Он состоит из нескольких свойств, Id, Name, GroupId, в нем также содержатся несколько статических методов: выбрать всех студентов, выбрать конкретного студента по Id, выбрать всех студентов в определенной группе. То есть с этим классом мы можем работать как на уровне его объектов, например, создавать конкретного студента. Также мы можем работать с этим классом в статическом контексте, например, можем выбрать всех студентов из базы данных.

Давайте проанализируем этот класс, сколько у него обязанностей. На первый взгляд может показаться, что у этого класса всего одна обязанность. То есть он представляет сущность «студент» и все что с ним связано. Но на самом деле у этого класса две обязанности. Во-первых, он представляет непосредственно сущность «студент», то есть описывает эту сущность с помощью свойств. Во-вторых, этот класс также знает, как работать с базой данных, знает как извлекать записи и как сохранять записи в базу данных.

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

Чтобы решить проблему нам опять же нужно разделить обязанности этого класса на два других класса. Это мы и сделали.

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

Также мы создали класс StudentsRepository. Вот как раз-таки этот класс теперь и отвечает за доступ к базе данных.

Вот что из себя представляет принцип единственной ответственности. У класса должна быть только одна причина для изменения. Очень важно проанализировать класс и правильно определить все его обязанности. После этого разделить обязанности на отдельные классы.

Важно вовремя заметить тот момент, когда класс из простого и безобидного начинает превращаться в сложный, начинает брать на себя слишком много ответственности. Пример с классом Service – это довольно распространенный пример. Этот класс может и умеет все. Помимо перечисленных методов сюда также могут входить любые другие, например, определение курса валют, работа с текстом, работа с файлами и так далее.

А начинается все совершенно безобидно. К примеру, мы создаем новое приложение, которое будет работать с графикой. И создаем в нем служебный класс Service, где определяем методы для работы с графикой. Постепенно, со временем, наше приложение изменяется, совершенствуется, также изменяется и класс Service. Вдруг нам стало необходимо еще отправлять email-письма, и мы добавляем в класс Service новый функционал. Еще через какое-то время мы решили создавать XML-файлы, и опять включили новый функционал в класс Service. Получается, с каждым новым изменением этот класс Service начинает брать на себя все больше и больше обязанностей. Поэтому очень важно вовремя заметить этот момент и реализовать принцип единственной ответственности.

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

То есть получается новый класс ImageService по-прежнему имеет несколько обязанностей. Работа с изображениями – это одна обязанность, сохранение\загрузка изображений – это другая обязанность. Вывод напрашивается сам собой. Нам опять же нужно будет разделить эти обязанности по разным классам.

И еще одно очень важное замечание. Оно касается абсолютно всех принципов, в том числе и этого. Не стоит применять этот принцип без необходимости. То есть не надо его применять просто потому что он есть.

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

В завершении урока давайте подведем небольшой итог. Принцип единственной ответственности – один из самых простых, но при этом его трудно применять правильно. Сочетание обязанностей для нас выглядит совершенно естественно. Их выявление и разделение как раз и является одной из задач, которая встает перед нами. Если мы хотим, чтобы при постоянных изменениях программы дизайн всегда оставался удобным и простым, нам нужно своевременно применять принцип единственной ответственности.

Источник

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

Принцип единственной ответственности — The Single Responsibility Principle или SRP — один из пяти основных принципов объектно-ориентированного программирования и проектирования, сформулированных Робертом Мартином.

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

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

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

Следование SRP весьма полезная практика с точки зрения повторного использования кода. Сложные объекты с комплексными зависимостями обычно очень сложно использовать повторно, особенно если нужна только часть реализованного в них функционала. А небольшие классы с чётко очерченным функционалом, напротив, проще использовать повторно, так как они не избыточные и редко тянут за собой существенный объём зависимостей.

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

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

Например, часто используемый во фреймворках паттерн ActiveRecord нарушает принцип единственной ответственности. ActiveRecord реально объединяет в себе очень много функциональных возможностей и часто смешивает бизнес-логику и работу со слоем хранения. При этом использование ActiveRecord часто является удобным и целесообразным. На этом примере становится ясно, что SRP — это не догма, а нарушение этого принципа вполне может быть логичным и целесообразным.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *