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

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

Роберт Сесил Мартин — тот, кто собрал эти концепции и представил их как принципы SOLID в 2004 году. В своей книге Чистая архитектура он подробно рассказывает об этих концепциях. В этом посте мы углубимся и посмотрим, как мы можем использовать этот принцип в наших приложениях, заблуждениях и примерах.

Принцип единой ответственности (SRP)

Модуль должен иметь одну и только одну причину для изменения

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

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

Сплоченность – это то, насколько тесно все подпрограммы в классе или весь код в подпрограмме поддерживают главную цель — насколько класс сфокусирован. Сплоченность означает, что все вещи, которые идут вместе, должны оставаться вместе. Например, если у вас есть пользовательские модули, имеет смысл поместить все связанные с пользователем сущности, службы и контроллеры в пользовательский модуль. Если у вас есть несколько методов для добавления, обновления и удаления профилей пользователей, имеет смысл поместить эти методы в класс UserProfileController. Мы можем применить Cohesion к любому уровню приложения.

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

Примеры принципа единой ответственности

  1. Spring framework содержит множество модулей, таких как spring-core, spring-beans, spring-jdbc и spring-aop. Эти модули разделены в соответствии с определенным набором функций. Каждый модуль имеет свое назначение.
  2. Интерфейс Spring Framework ApplicationContext — это центральный интерфейс, обеспечивающий настройку приложения Spring.
  3. Java Persistence API предоставляет разработчикам Java средство объектно-реляционного сопоставления для управления реляционными данными в приложениях Java. Прежде чем использовать JPA для сохранения данных, мы должны использовать другие платформы для извлечения, проверки и регистрации данных, но JPA используется только для управления реляционными данными.
  4. Архитектура микросервисов, которая стала популярной несколько лет назад, также является отличным примером SRP, поскольку она пытается сгруппировать несколько API и создавать небольшие сервисы, которые можно масштабировать, развертывать и управлять независимо.

Нарушения принципа единой ответственности

  1. Пакет java.util содержит разные типы классов. Когда вы создаете такие пакеты, как утилиты или помощники, любой может поместить в эти пакеты любой класс, который кажется ему подходящим. Вместо таких имен всегда следует использовать более осмысленные слова.
  2. стандартная библиотека. h в C++ аналогичен пакету util в Java. Кроме того, он имеет неоднозначное имя и содержит множество функций, которые делают разные вещи.

Как применить СРП

  1. Такие атрибуты, как имя, фамилия, адрес и электронная почта, являются атрибутами пользователя и должны находиться в классе User.
  2. Если ваше приложение имеет несколько доменов, разделите их на разные модули. Например, давайте рассмотрим банковское приложение. Он может иметь такие модули, как пользовательский модуль, модуль учетной записи, кредитный модуль и модуль безопасности.
  3. Создавайте согласованные микросервисы, следуя дизайну, ориентированному на предметную область.

Принцип открытого-закрытого (OCP)

Программный артефакт должен быть открыт для расширения, но закрыт для модификации.

OCP был первоначально опубликован Бертраном Мейером в 1980-х годах.

Ниже приведены два утверждения, взятые из второго издания книги Бертрана Мейера «Объектно-ориентированное проектирование программного обеспечения». Он был опубликован в 2000 году, но первое издание вышло в 1980-х годах.

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

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

общедоступные и опубликованные интерфейсы

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

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

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

«Закрыто для внесения изменений» означает публикацию должным образом протестированного и стабильного программного обеспечения. В 80-х не было адекватных инструментов контроля версий, но сегодня у нас есть много вариантов, поэтому ваше приложение вряд ли закроют; у вас должен быть правильный механизм управления версиями и выпусками. Например, если вы поддерживаете REST API, вы можете либо иметь механизм управления версиями, либо добавлять новые функции в порядке обратной совместимости.

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

Шаблоны проектирования — это общие, многократно используемые решения часто возникающих проблем в заданном контексте. Шаблоны проектирования позволяют нам следовать ОСР и имеют много других преимуществ.

Пример (шаблон адаптера)

Адаптер — это структурный шаблон проектирования, который позволяет объектам с несовместимыми интерфейсами взаимодействовать.

Представьте, что у вас есть приложение, которое обрабатывает XML-файлы с биржевыми данными и отображает данные в удобном для пользователей интерфейсе. Далее вы получаете новое требование о том, что вы должны показывать аналитику. К сожалению, сторонняя библиотека аналитики, которую вы надеетесь использовать, принимает входные данные только в формате JSON.

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

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

Шаблон адаптера позволяет нам вводить адаптеры, не нарушая существующий код.

Принцип замещения Лисков (LSP)

Здесь требуется что-то вроде следующего свойства подстановки: если для каждого объекта o1 типа S существует объект o2 типа T, такой что для всех программ P, определенных в терминах T, поведение P не изменяется, когда o1 заменяется на o2, тогда S является подтипом T.

Приведенное выше определение было опубликовано в статье Барбары Лисков в 1987 году.

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

Пример LSP

В Java ArrayList и LinkedList реализуют интерфейс List. Например, если вы используете LinkedList(S в определении) через интерфейс List где-то в своем коде, его можно заменить на ArrayList(T в определении). Эта замена не изменит ваше поведение кода.

  • ArrayList add: Добавляет указанный элемент в конец этого списка.
  • LinkedList add: Добавляет указанный элемент в конец этого списка.

Пример без LSP

Set не является подтипом списка, и обратное неверно. Добавление элемента в набор отличается от добавления элемента в список. Несмотря на то, что они оба имеют одни и те же методы добавления, в каждой структуре данных они делают две разные вещи.

  • Добавление элемента в Набор: Добавляет указанный элемент в этот набор, если он еще не присутствует.
  • Добавление элемента в Список: Добавляет указанный элемент в конец этого списка.

Программа для интерфейса, а не реализации

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

Наследование и интерфейсы позволяют нам создать семейство связанных объектов и использовать полиморфизм.

Пример: интерфейсы Java List имеют множество реализаций, и мы можем выбрать любую реализацию, которая нам нравится. Если вам нужен потокобезопасный список, вы можете использовать реализацию CopyOnWriteArrayList. В противном случае вы можете использовать ArrayList, который не является потокобезопасным, но быстрее.

List<String> strList1 = new ArrayList<>();

List<String> strList2 = new CopyOnWriteArrayList<>();

Такой подход снижает зависимость реализации между подсистемами. При таком подходе клиентский код не знает о реализации. Клиентский код знает, что он делает, а не как он это делает.

Чтобы извлечь выгоду из этого подхода, нам нужно следовать LSP, потому что изменение реализации не должно изменить того, что делает метод.

Принцип разделения интерфейсов (ISP)

Клиентов не следует заставлять зависеть от интерфейсов, которые они не используют.

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

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

Примеры интернет-провайдеров

  1. Java LinkedList реализует несколько небольших интерфейсов, таких как Queue и List. Эти интерфейсы группируют методы, которые тесно связаны между собой.
  2. Интерфейс Java Queue реализуется многими классами, такими как LinkedBlockingQueue и LinkedList. Однако интерфейс очереди имеет только методы для управления структурой данных очереди.

Принцип инверсии зависимостей (DIP)

Мы можем изменить (инвертировать) направление любой зависимости в нашей кодовой базе.

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

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

Как инвертировать зависимости?

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

На приведенной выше диаграмме левая сторона показана до применения DIP. Уровень представления вызывает уровень домена, а уровень домена вызывает уровень сохраняемости.

Правая сторона показывает после применения DIP. Мы представили интерфейс для уровня сохраняемости на уровне предметной области, и реализация будет находиться на уровне сохраняемости. Следовательно, мы инвертировали зависимости.

Мы можем использовать такие инструменты, как ArchUnit, для проверки зависимостей между слоями/пакетами и интегрировать процесс проверки в набор тестов.

Заключение

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

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

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

Например, компания может создать поговорку; Класс Java не может содержать более 250 строк кода. Но команда/разработчик должны решить, сколько интерфейсов необходимо при реализации варианта использования. Таким образом, проверка кода в команде и наличие набора рекомендаций при проверке кода — отличный способ убедиться, что вы следуете передовым методам, включая SOLID.

Ниже приведены несколько примеров руководств/руководств по стилю, созданных Google для своих проектов.

  1. Документация по инженерным практикам Google
  2. Руководство по стилю Google Java
  3. Стиль кода Java AOSP для участников

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

Посетите мой веб-сайт, чтобы увидеть другие мои публикации.



Ссылка