Принципы SOLID — это ваше руководство по разработке хорошо спроектированных систем.
Эти принципы позволяют разрабатывать удобные в сопровождении, расширяемые и простые для понимания приложения. Без них ваш код может стать жестким и хрупким. Любые небольшие изменения в программе могут привести к ошибкам.
"S" в принципах SOLID означает Принцип единой ответственности, который будет основной темой обсуждения в этой статье.
Проблема в том, что нас обычно учат этим принципам в школах или университетах с примерами транспортных средств или квадрата/прямоугольника и неточной терминологией без какой-либо очевидной закономерности, позволяющей определить необходимость SRP.
В этой статье я выйду за рамки этого и объясню SRP с более практической точки зрения, научу вас шаблону для выявления нарушений и необходимости SRP, а также приведу соответствующие примеры. Так что ничего не пропускайте и наслаждайтесь чтением :)
Проблема с интернет-определениями
Наиболее распространенное определение SRP, которое вы найдете:
Каждый класс в вашем приложении должен иметь только одну обязанность
Это определение может показаться рациональным, учитывая название этой концепции, однако оно ставит вопрос о том, что на самом деле определяет ответственность. И, тем не менее, нет четкого определения термина «ответственность», что является серьезной проблемой, поскольку было бы практически невозможно рассуждать о в противном случае отдельные обязанности.
Например, разные разработчики могут иметь совершенно различные представления о том, что представляет собой ответственность, а затем обнаруживать совершенно разные обязанности в одном и том же фрагменте кода.
Еще одно расплывчатое определение, которое вы встретите в Интернете, заключается в следующем:
Вы должны уметь описывать каждый класс, не используя слово "и"
Давайте посмотрим на этот код.
Если я применю первое определение, я бы сказал, что у этого класса есть две разные обязанности: «createMirrorImage» и «WatermarkImage», таким образом, нарушая SRP, что явно необоснованно, учитывая, что вся цель этого класса состоит в манипулировании изображениями.
Согласно второму определению, «этот класс создает зеркальные изображения и водяные знаки». Поскольку в описании есть слово «и», я нарушаю принцип единой ответственности? Но если я определяю его как «этот класс предназначен для управления изображениями» без слова «и», это больше не нарушает ???.
Это абсурд, потому что я всегда могу описать класс, не используя слово «и»; таким образом, ни один из моих классов в моем приложении не будет нарушать SRP!.
Следовательно, эти два определения не применимы.
Практическое определение:
Наиболее применимое определение SRP будет следующим:
У класса должна быть одна и только одна причина для изменения. - Роберт С. Мартин
Это определение выступает за то, чтобы каждый класс выполнял только одну обязанность. Это не означает, что класс должен иметь только один метод или свойство; скорее, эти методы/свойства должны быть тесно связаны с ответственностью класса. Класс с многочисленными целями должен быть разделен на несколько классов.
Давайте кратко вернемся к первому примеру:
Используя третье определение, я не вижу причин менять этот класс, поскольку эти две функции работают для достижения одной и той же цели: манипулирования изображениями. Следовательно, это не нарушает SRP.
Давайте применим концепцию «причин для изменения» на практике, рассмотрев три примера из разных ситуаций.
Пример 1: Неясная цель класса
Глядя на него, я не могу определить, какова единственная цель этого класса. Похоже, что он служит несвязанным целям.
Давайте найдем «причины для изменения»:
- Новое требование добавить другой тип счета: generateInvoice().
- Просили изменить шаблон электронной почты в зависимости от типа бронирования: sendCustomerEmail().
- Запрошено сначала проверить кеш при обнаружении существующего резервирования: findExistingReservation().
Хотя я могу придумать много «причин для изменения», вышеизложенное кажется разумным, и поскольку у меня более одной причины, это нарушает SRP. Это почти наверняка расширит класс «Reservation», который сложно поддерживать и который приводит к тесной связи.
Две концепции, которые идут рука об руку с SRP:
Сплоченность — насколько тесно все связано друг с другом.
Соединение — это процесс, посредством которого все связано вместе.
Следовательно, этот класс тесно связан и имеет низкую связность.
Цель состоит в том, чтобы добиться слабой связанности и большей согласованности, что помогает добиться большей адгезии к SRP.
Давайте реструктурируем его, классифицируя эти три обязанности и обновляя класс Reservation, чтобы он просто предоставлял идентификатор и статус бронирования.
Пример 2: Четкая цель класса
У этого класса есть только одна цель: управление аутентификацией агента. Однако этот класс может нарушать SRP. Прежде чем продолжить чтение, можете ли вы придумать какие-либо причины для изменений?
Давайте посмотрим на «причины для изменения», которые могут произойти:
- Этот метод хеш-пароль класса является закрытым. И если я захочу изменить алгоритм хеширования в будущем, мне придется изменить этот класс.
- Он также проверяет, существует ли агент в базе данных. Если есть изменения в том, как мы определяем агент, или если нам нужно настроить настройки базы данных или читать из другой БД, нам потребуется изменить этот класс.
SRP нарушен, так как у меня есть более одной причины для изменения. И я могу утверждать, что мой класс более тесно связан. Однако он уже в высшей степени связный, поскольку все они участвуют в одной четко определенной задаче (аутентификация агента).
Итак, давайте слабо свяжем его, делегировав задачи отдельному классу.
Этот класс теперь имеет два закрытых поля, «agentDao» и «hashHelper», которые обеспечивают функции извлечения данных и хеширования соответственно. В результате он слабо связан и не нарушает SRP.
Разве нет больше причин для изменения класса AgentAuthManager?
Вы можете возразить, что если мы хотим отправить электронное письмо при входе в систему, не изменим ли мы тогда функцию входа в этот класс и тем самым нарушим SRP??
Ответ - да! Это возможно! Хотя у вас действительно может быть множество причин для изменений, тем не менее, мы должны вести себя прагматично и рассматривать только те изменения, которые, скорее всего, произойдут. В противном случае вы рискуете усложнить разработку и потратить время на доработку, когда в этом нет необходимости. Помните, что вы не можете учитывать все каждые мыслимые будущие обстоятельства при создании приложения.
Пример 3: запрос новой функции
Предположим, вас попросили разработать функцию маршрута для приложения для управления поездками, которое разрабатывает ваша организация, в котором вы должны подключить несколько сторонних API от отелей и авиакомпаний, принимать платежи от различных поставщиков и сохранять бронирования в базе данных MySQL. Однако поддержка облачной базы данных может быть введена позже.
Итак, без принципа единой ответственности это выглядело бы так:
Эти интеграции и методы API для сохранения бронирований и получения платежей будут использоваться в классе Itinerary. В результате этот класс, скорее всего, будет расти, и им станет трудно управлять.
давайте рассмотрим причины для изменения:
- Запрос на поддержку нового отеля или рейса — новую интеграцию API или функцию.
- Интегрируйте новый способ оплаты — например, Apple Pay.
- Добавьте облачную базу данных в качестве дополнительной базы данных.
Рефакторинг архитектуры после SRP будет выглядеть следующим образом:
Ключевые вещи, которые нужно помнить
- Некоторые преимущества SRP включают более простое тестирование, повторно используемый код, лучшие решения по реализации и адаптивное кодирование.
- Реализация класса может различаться по причинам производительности или архитектуры, так как требования со временем меняются. Если ваши классы не придерживаются принципа единой ответственности, вы, скорее всего, создадите «богоподобные» классы с тысячами строк и несколькими задачами.
- Чтобы избежать чрезмерного проектирования, ищите только те причины изменений, которые могут произойти. Мы не можем предвидеть каждое изменение в будущем.
Что дальше
Надеюсь, вам понравилась первая статья серии SOLID Principle. Пока я работаю над оставшимися концепциями, вам могут быть интересны предыдущие статьи, которые я написал:
- 3 шаблона проектирования, которые должен изучить каждый разработчик
- JavaScript под капотом: передовые концепции, которые должны знать разработчики
- Стек и инструменты, которые необходимо освоить, чтобы быстрее стать полноценным разработчиком
Кроме того, подпишитесь на Medium и LinkedIn, чтобы быть в курсе новых сообщений, которые я создаю. Ваше здоровье!
Бит: почувствуйте мощь компонентно-ориентированной разработки
Скажи привет Bit. Это инструмент №1 для разработки приложений на основе компонентов.
С помощью Bit вы можете создать любую часть своего приложения в виде «компонента», который можно компоновать и использовать повторно. Вы и ваша команда можете совместно использовать набор компонентов для более быстрой и последовательной совместной разработки большего количества приложений.
- Создавайте и компонуйте «строительные блоки приложения»: элементы пользовательского интерфейса, полные функции, страницы, приложения, бессерверные или микросервисы. С любым стеком JS.
- С легкостью делитесь и повторно используйте компоненты в команде.
- Быстро обновляйте компоненты в разных проектах.
- Делайте сложные вещи простыми: Монорепо, дизайн-системы и микрофронтенды.
Попробуйте Bit бесплатно и с открытым исходным кодом→