Как с помощью принципа единственной ответственности писать гибкий и модульный код

Posted by

Если вы занимались разработкой ПО, вам наверняка знакома аббревиатура SOLID.

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

Расшифровка аббревиатуры:

— Single Responsibility Principle (SRP) — принцип единственной ответственности. Каждый класс выполняет только свои задачи.
— Open-Closed Principle (OCP) — принцип открытости/закрытости — программные сущности (классы, модули, функции и пр.) должны быть открыты для расширения, но закрыты для модификации.
— Liskov Substitution Principle (LSP) — принцип подстановки Барбары Лисков гласит: «наследующий класс должен дополнять, а не изменять базовый».
— Interface Segregation Principle (ISP) — принцип разделения интерфейса: «много интерфейсов, специально предназначенных для клиентов, лучше, чем один интерфейс общего назначения».
— Dependency Inversion Principle (DIP) — принцип инверсии зависимостей: «абстракции не должны зависеть от деталей — детали должны зависеть от абстракций».

В данном материале мы рассмотрим SRP — принцип единственной ответственности.

Немного предыстории

Роберт Мартин изначально ввёл термин в качестве составляющей своего труда «Принципы объектно-ориентированного проектирования». В основу SRP Мартина легла закономерность связности, описанная Томом Демарко и Мейлиром Пейдж-Джонсоном.

Кроме того, в разработке ПО есть два схожих понятия – инкапсуляция и сокрытие информации. SRP включает в себя также и эти два (или одно) понятия от Дэвида Парнаса, который обозначил их примерно так: «декомпозиция системы на модули не должна основываться на анализе блок-схем или потоков исполнения. Вместо этого, каждый модуль должен содержать внутри некоторое решение (design decision), предоставляя минимальное количество информации о нём своим клиентам».

Суть SRP

Суть SRP в одном предложении: «соберите всё, изменяемое по одной и той же причине, но разделите изменяемое по разным причинам».

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

«Божественный объект»

!

Лучший способ изучить SRP — увидеть его в действии. Ниже показан пример программы на Ruby, не соответствующей принципу единственной ответственности. Код описывает поведение и атрибуты космической станции. Посмотрите на него и попробуйте определить:

— обязанности объектов, конкретизированные в классе SpaceStation;
— виды лиц, которые могут быть заинтересованы в деятельности космической станции.

Видно, что класс SpaceStation имеет несколько «функций» или «задач»:

— работа с сенсорами;
— использование расходных материалов;
— расход топлива;
— использование подруливающих двигателей.

Субъекты не указаны в коде, но можно предположить, что это:

— научный работник, управляющий сенсорами;
— специалист по материально-техническому обеспечению;
— лицо, ответственное за запасы топлива;
— пилот.

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

Обратимся к примеру с космической станцией. Представьте, если надо добавить медицинский отсек, а из-за этого произойдут какие-нибудь проблемы с топливным. Попробуйте представить, что для того, чтобы шагнуть, вам нужно выгнуть левую руку назад, повернуть голову вправо и нагнуться. Работает? Да. Терпимо? Возможно. А если нужно бежать? А если с вёдрами? То же самое и с проектом — он будет работать, но до определённого момента.

Нарушение SRP может быть выгодно в начале проекта, но это лишь краткосрочно. С ростом проекта будут увеличиваться финансовые и временны́е ресурсы для исправления существующих проблем. Влияющие друг на друга участки кода, его громоздкость и нечитаемость — главные проблемы, о которых надо подумать при игнорировании SRP.

Разбивка по обязанностям

Выше были определены 4 функции станции, которые управлялись классом SpaceStation. Они и будут отправной точкой рефакторинга кода. Теперь программа чуть больше соответствует SRP.

Теперь класс SpaceStation скорее служит контейнером, внутри которого выполняются операции для подчинённых частей:

— набора сенсоров;
— системы подачи расходных материалов;
— топливного бака;
— подруливающих двигателей.

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

Sensors (сенсоры);
SupplyHold (поставки расходных материалов);
FuelTank (топливный бак);
Thrusters (подруливающие двигатели).

В этой версии кода есть некоторые важные отличия от предыдущей, а именно: отдельные элементы функциональности не только инкапсулированы в собственные классы, но и организованы таким образом, чтобы быть предсказуемыми и последовательными. Идея заключается в группировке сходных по функциональности элементов, для следования принципу связности, и в изолировании данных таким образом, чтобы они были доступны только для соответствующих субъектов. Если понадобится изменить принцип работы системы поставки с хэш-структуры на массив, то это легко можно сделать, используя класс SupplyHold. Таким образом другие модули не будут затронуты. Причём класс SpaceStation даже не будет догадываться о действиях, производимых в модулях.

Заметьте, что сейчас в коде выше есть методы report_supplies и report_fuel, содержащиеся в классах SupplyHold и FuelTank. Что, если с Земли попросят изменить механизм загрузки отчётов? Придётся редактировать оба предыдущих класса. А если руководство решит изменить технологию доставки топлива и расходных материалов? Кажется, придётся повторно изменять те же классы. Похоже на нарушение SRP. Надо бы исправить.

В последней версии программы обязанности «модулей» были разбиты на два дочерних класса FuelReporter и SupplyReporter, объединённых под родительским классом Reporter. Далее были добавлены экземплярные переменные к классу SpaceStation, чтобы запустить соответствующий Reporter. Если руководству с Земли понадобится ещё что-то изменить, то можно внести правки в подклассы, не влияя на работу объектов (классов), о которых они докладывают.

Конечно, до сих пор есть некоторая связь между разными классами. Например, SupplyReporter зависит от SupplyHold, так же зависим и FuelReporter от FuelTank. Кроме того, подруливающие двигатели тоже должны быть связаны с топливным баком. Все эти связи кажутся довольно логичными и на этом уровне уже можно изменять код одного объекта, не влияя на другой (либо влияя незначительно).

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

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

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