Принципы SOLID — это ваше руководство по разработке хорошо спроектированных систем.

Эти принципы позволяют разрабатывать удобные в сопровождении, расширяемые и простые для понимания приложения. Без них ваш код может стать жестким и хрупким. Любые небольшие изменения в программе могут привести к ошибкам.

"S" в принципах SOLID означает Принцип единой ответственности, который будет основной темой обсуждения в этой статье.

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

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

Проблема с интернет-определениями

Наиболее распространенное определение SRP, которое вы найдете:

Каждый класс в вашем приложении должен иметь только одну обязанность

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

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

Еще одно расплывчатое определение, которое вы встретите в Интернете, заключается в следующем:

Вы должны уметь описывать каждый класс, не используя слово "и"

Давайте посмотрим на этот код.

Если я применю первое определение, я бы сказал, что у этого класса есть две разные обязанности: «createMirrorImage» и «WatermarkImage», таким образом, нарушая SRP, что явно необоснованно, учитывая, что вся цель этого класса состоит в манипулировании изображениями.

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

Это абсурд, потому что я всегда могу описать класс, не используя слово «и»; таким образом, ни один из моих классов в моем приложении не будет нарушать SRP!.

Следовательно, эти два определения не применимы.

Практическое определение:

Наиболее применимое определение SRP будет следующим:

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

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

Давайте кратко вернемся к первому примеру:

Используя третье определение, я не вижу причин менять этот класс, поскольку эти две функции работают для достижения одной и той же цели: манипулирования изображениями. Следовательно, это не нарушает SRP.

Давайте применим концепцию «причин для изменения» на практике, рассмотрев три примера из разных ситуаций.

Пример 1: Неясная цель класса

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

Давайте найдем «причины для изменения»:

  1. Новое требование добавить другой тип счета: generateInvoice().
  2. Просили изменить шаблон электронной почты в зависимости от типа бронирования: sendCustomerEmail().
  3. Запрошено сначала проверить кеш при обнаружении существующего резервирования: findExistingReservation().

Хотя я могу придумать много «причин для изменения», вышеизложенное кажется разумным, и поскольку у меня более одной причины, это нарушает SRP. Это почти наверняка расширит класс «Reservation», который сложно поддерживать и который приводит к тесной связи.

Две концепции, которые идут рука об руку с SRP:

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

Следовательно, этот класс тесно связан и имеет низкую связность.

Цель состоит в том, чтобы добиться слабой связанности и большей согласованности, что помогает добиться большей адгезии к SRP.

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

Пример 2: Четкая цель класса

У этого класса есть только одна цель: управление аутентификацией агента. Однако этот класс может нарушать SRP. Прежде чем продолжить чтение, можете ли вы придумать какие-либо причины для изменений?

Давайте посмотрим на «причины для изменения», которые могут произойти:

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

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

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

Этот класс теперь имеет два закрытых поля, «agentDao» и «hashHelper», которые обеспечивают функции извлечения данных и хеширования соответственно. В результате он слабо связан и не нарушает SRP.

Разве нет больше причин для изменения класса AgentAuthManager?

Вы можете возразить, что если мы хотим отправить электронное письмо при входе в систему, не изменим ли мы тогда функцию входа в этот класс и тем самым нарушим SRP??

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

Пример 3: запрос новой функции

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

Итак, без принципа единой ответственности это выглядело бы так:

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

давайте рассмотрим причины для изменения:

  1. Запрос на поддержку нового отеля или рейса — новую интеграцию API или функцию.
  2. Интегрируйте новый способ оплаты — например, Apple Pay.
  3. Добавьте облачную базу данных в качестве дополнительной базы данных.

Рефакторинг архитектуры после SRP будет выглядеть следующим образом:

Ключевые вещи, которые нужно помнить

  1. Некоторые преимущества SRP включают более простое тестирование, повторно используемый код, лучшие решения по реализации и адаптивное кодирование.
  2. Реализация класса может различаться по причинам производительности или архитектуры, так как требования со временем меняются. Если ваши классы не придерживаются принципа единой ответственности, вы, скорее всего, создадите «богоподобные» классы с тысячами строк и несколькими задачами.
  3. Чтобы избежать чрезмерного проектирования, ищите только те причины изменений, которые могут произойти. Мы не можем предвидеть каждое изменение в будущем.

Что дальше

Надеюсь, вам понравилась первая статья серии SOLID Principle. Пока я работаю над оставшимися концепциями, вам могут быть интересны предыдущие статьи, которые я написал:

Кроме того, подпишитесь на Medium и LinkedIn, чтобы быть в курсе новых сообщений, которые я создаю. Ваше здоровье!

Бит: почувствуйте мощь компонентно-ориентированной разработки

Скажи привет Bit. Это инструмент №1 для разработки приложений на основе компонентов.

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

  • Создавайте и компонуйте «строительные блоки приложения»: элементы пользовательского интерфейса, полные функции, страницы, приложения, бессерверные или микросервисы. С любым стеком JS.
  • С легкостью делитесь и повторно используйте компоненты в команде.
  • Быстро обновляйте компоненты в разных проектах.
  • Делайте сложные вещи простыми: Монорепо, дизайн-системы и микрофронтенды.

Попробуйте Bit бесплатно и с открытым исходным кодом→

Узнать больше