Баю Фебри Асморо — 25 мая 2022 г.

Если вы освоили и использовали ООП, вам также необходимо изучить SOLID, потому что это две разные вещи.

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

Тогда как SOLID — это принцип. SOLID — это мнемоническая аббревиатура пяти принципов проектирования, предназначенных для того, чтобы сделать дизайн программного обеспечения более понятным, гибким и удобным в сопровождении. Принципы представляют собой подмножество многих принципов, продвигаемых американским инженером-программистом и инструктором Робертом С. Мартином (также известным как дядя Боб), впервые представленным в его статье 2000 года «Принципы проектирования и шаблоны проектирования».

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

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

S — единая ответственность

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

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

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

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

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

Применяя принципы SRP (Single Responsibility Pricinple), мы можем выделить обязанности каждой функции в новый класс. Итак, результат будет таким:

После рефакторинга распределение обязанностей для каждого класса было разделено в зависимости от актера.

О — Открыто/Закрыто

Объекты или сущности должны быть открыты для расширения, но закрыты для модификации.

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

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

Ниже приведен пример примера расчета скорости автомобиля:

Приведенный выше пример изменяет скорость транспортного средства в соответствии с заданной рабочей командой. Это не подходит для OCP (принцип открытости и закрытости), потому что этот метод делает разные вещи в зависимости от действия, которое передается функции getCurrentSpeed. По мере появления новых запросов действия потребуют добавления новых условий if-else. Это показывает, что метод не закрыт для изменения.

Мы можем улучшить это, абстрагировав действие транспортного средства из функции. Давайте сначала создадим абстрактный класс транспортного средства. Этот класс абстрактно что-то делает. Затем мы получаем 2 класса из абстрактного класса транспортного средства;

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

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

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

L — Замена Лискова

Пусть q(x) — доказуемое свойство объектов x типа T. Тогда q(y) должно быть доказуемо для объектов y типа S, где S — подтип T.

LSP (принцип подстановки Лискова) — это правило, которое применяется к иерархии наследования. Это требует, чтобы мы проектировали наши классы так, чтобы зависимости между клиентами могли быть заменены так, чтобы клиент не знал об изменениях. Следовательно, все подклассы могут работать так же, как и их суперклассы.

Вот пример кода сценария, который мы будем использовать:

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

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

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

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

I — Разделение интерфейса

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

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

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

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

Есть пример между квадратом и кубом. Оба относятся к типу кильватерного поля, но имеют ли они одинаковую формулу? Рассмотрим пример применения вычислений к следующим классам Square и Cube.

Квадратная область моделируется как класс Square, а куб моделируется как класс Cube. Они являются частью ShapeInterface следующим образом:

Внутри ShapeInterface есть две функции, в том числе площадь и объем. Когда класс Square реализует этот интерфейс, все функции ShapeInterface будут переопределены в классах Square. Однако есть кое-что не совсем правильное, а именно есть функция объема, тогда как в реальности квадрат не должен уметь вычислять объем. Как мы можем решить эту проблему?

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

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

D — Инверсия зависимости

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

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

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

Давайте сначала рассмотрим пример использования ОС Android и смартфона, который моделируется в коде AndroidOS и класса Smartphone следующим образом:

На основе приведенного выше кода существует класс ОС Android, который является конструктором для добавления смартфона, в данном случае он смоделирован как класс Smartphone. Однако, что делать, если дело в той же модели ОС Android, хотите изменить тип ОС. В этом случае хочу заменить его на iPhone OS.

Мы не можем его реализовать, потому что параметром конструктора класса ОС Android является класс Smartphone. Если мы введем класс ОС iPhone, результатом будет ошибка.

Один из способов решить эту проблему — применить DIP (принцип инверсии зависимостей). Во-первых, мы сначала создаем OSInterface следующим образом:

Применяя DIP, мы можем создать гибкую систему. Где зависимости или зависимости от исходного кода относятся только к абстракциям, а не к конкрециям (классу). В приведенном выше примере это показано в виде OSInterface, содержащего метод запуска. Итак, после применения этого принципа высокоуровневый класс не работает напрямую с низкоуровневым классом, а использует интерфейс как слой абстракции.

Краткое содержание

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

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