Принципы SOLID - это очень важные принципы проектирования программного обеспечения, представленные Робертом Мартином, также известным как дядя БОБ в этой статье. Скорее, это 5 принципов, выбранных из большого количества принципов. Теперь, когда ваше внимание привлекла эта великолепная Ferrari 458, давайте начнем с принципов SOLID. Принципы заключаются в следующем:

  • Принцип единой ответственности (SRP)
  • Принцип открытости-закрытости (OCP)
  • Принцип замещения Лискова (LSP)
  • Принцип разделения интерфейса (ISP)
  • Принцип инверсии зависимостей (DIP)

Эти принципы решают основные проблемы плохой архитектуры, такие как:

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

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

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

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

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

Предположим, у вас есть автомобиль, показанный выше (конечно, вы ветеран процесса!), И вы планируете совершить длительную поездку на нем, но перед этим вам нужно все настроить, поэтому вы выполните 3 действия. Вы запускаете двигатель, температура сегодня высокая, вы включаете кондиционер, и теперь, когда вы чувствуете себя хорошо, включаете музыку и бум, все готово. Давайте запрограммируем это:

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

Принцип открытости-закрытости

По умолчанию . Программные объекты (классы, модули, функции и т. д.) должны быть открыты для расширения, но закрыты для модификации ».

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

Давайте посмотрим на принцип открытости и закрытости (OCP) на практике. Продолжая рассказ, после поездки вы добираетесь до своего района и вместо того, чтобы спешить прямо к своей кровати, вы решаете встретиться с Полом, вашим другом-механиком. У Пола есть несколько действительно классных аксессуаров для вашего спортивного автомобиля: спойлер, сиденья (Id: 5), стильные колеса, мост и приборная панель. Вы купили их, и теперь Пол их устанавливает. Давайте запрограммируем это:

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

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

Справа рассмотрим сценарий по пунктам. // 1: поддерживается отдельный класс Accessory, который можно считать стандартом для всех аксессуаров. Класс Accessory наследуется от customStringConvertible, что обязывает его реализовать необходимое описание функциональности для установки аксессуара. Никаких изменений в этот класс никогда не вносится. // 2. Учтите, что у Пола нет сидений Ferrari, которые предполагалось установить, а есть сиденья другой совместимой марки, идентифицируемой с помощью Id: 5. Это не проблема, поскольку наш класс открыт для расширений, мы просто пишем код для установки идентификатора аксессуара: 5, готово! // 3: класс Ferrari458, теперь просто нужно установить аксессуары, не более того, он устанавливает их с помощью функции installAccessories () // 4: функция main () выполняет установка нескольких аксессуаров и успешно выполняет процесс.

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

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

Def. Вы всегда должны иметь возможность заменить подтип базовым типом.

LSP, названный в честь Барбары Лисков, говорит, что объекты суперкласса должны быть заменены объектами его подклассов без нарушения работы приложения. Для этого объекты подкласса должны вести себя точно так же, как ведут себя объекты суперкласса. Итак, после установки аксессуаров Пол расскажет вам об этой встрече клуба Ferrari, которая проходит в центре города, 11 часов вечера, и допускаются только настоящие Ferrari и их владельцы. Итак, вы решили его посетить, давайте напишем код:

Слева - класс FerrariX, который не дает ожидаемых результатов, как класс Ferrari, несмотря на то, что он аутентичен и унаследован от самого класса Ferrari. Это нарушает принцип. Сценарий завершился неудачно из-за плохой реализации, объект производного класса FerrariX должен был выполнить одну задачу, то есть вести себя как объект суперкласса Ferrari в identifyFerrari (), что он не смог сделать.

Справа объект класса Ferrari458 ведет себя в точности как объект класса Ferrari. Принцип подстановки Лискова явно заявляет, что если у вас есть функция, которая принимает базовые классы, то передача производных классов не должна нарушать функциональность. Сценарий справа полностью соответствует этому. В LSP нет ничего загадочного, это все, о чем говорит этот принцип!

Принцип разделения интерфейса

Def. Ни один клиент не должен зависеть от методов, которые он не использует.

Интернет-провайдер заявляет, что классы никогда не должны быть принуждены к реализации методов, которые им не нужны, этот принцип был сформулирован Робертом Мартином, когда он консультировал Xerox, и этот принцип гласит, что вместо использования одного толстого интерфейса используется множество более мелких интерфейсов. следует отдавать предпочтение сгруппированным методам. Давайте посмотрим на это на практике, так что теперь, когда вы участвуете в встрече, вы немного пообщаетесь, затем вы встречаетесь с Себастьяном, который является гонщиком F1, Себастьян начинает объяснять вам, что его машина F1 включает функцию турбонаддува, а его Автомобиль явно настроен для гонок, а не для любого другого режима. Давайте запрограммируем это:

Слева класс FerrariF1 наследует от единого протокола FerrariConfigurations, который имеет 3 различных режима движения, спортивный, круизный и гоночный, а также механизм для наддува двигателя. Себастьян - гонщик Формулы-1, и все, что ему нужно, - это гоночный режим и турбонаддув. Кроме того, автомобили F1, как правило, сильно оптимизированы, когда дело доходит до гонок, они являются крайними минималистами, когда речь идет о весе и динамике автомобиля. В том, что слева находится сценарий, нет ничего плохого, утомительно обрабатывать ошибки из-за ненужных конфигураций, это не рекомендуется интернет-провайдером и определенно не является оптимальным решением.

Справа: класс FerrariF1 соответствует требованиям ISP. // 1: изначально в этом скрипте поддерживается отдельный протокол для каждой конфигурации. Это один из способов реализации, функции группы в соответствии с требованиями могут быть поддержаны сразу же! // 2: Затем мы добавляем необходимые протоколы для F1_Ferrari_Config, это дает возможность для более систематической реализации нашего приложения. // 3: класс FerrariF1 теперь наследуется от протокола F1_Ferrari_Config, и на этот раз нашему классу не нужно реализовывать ненужные режимы. // 4: тестируем нашу реализацию. Это позволяет машине Себастьяна участвовать в гонках на полную мощность, а также соответствовать требованиям интернет-провайдера!

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

Def. Модули высокого уровня не должны зависеть от модулей низкого уровня, но они оба должны зависеть от абстракций (например, интерфейсов). Абстракции не должны зависеть от деталей, но детали (конкретные реализации) должны зависеть от абстракций.

Немного неприятное определение, но поверьте мне, мы тоже воспользуемся этим! Во-первых, этот принцип не имеет ничего общего с принципом внедрения зависимостей, поэтому не путайте эти два понятия! Во-вторых, принцип инверсии зависимостей (DIP) утверждает две вещи: DIP- 1. Модули высокого уровня не должны зависеть от модулей низкого уровня, но они оба должны зависеть от абстракций. DIP-2. Абстракции не должны зависеть от деталей, но детали (конкретные реализации) должны зависеть от абстракций. Давайте посмотрим на это на практике, это даст вам более яркую картину, а затем мы еще раз рассмотрим определение! Итак, когда вы разговариваете с Себастьяном, он рассказывает вам об одной гонке, когда 1. он снизил скорость, разогнался и настроился на крутой обгон, и в процессе 2. Он взорвал свой двигатель, поэтому ему пришлось остановиться, чтобы сменить двигатель и продолжить гонку. Давайте закодируем это:

Слева вы можете видеть, что модуль высокого уровня (FerrariF1) напрямую зависит от модулей низкого уровня (крутящий момент и трансмиссия). Это зависит не только от модулей низкого уровня, но и от внутренних компонентов (массивов операций). Это нарушает DIP-1. В этом скрипте наблюдается тесная связь, что вызывает проблемы с возможностью повторного использования кода. Это не то, что вам нужно, если вы собираетесь повторно использовать определенный класс 10 раз в проекте.

Справа все объекты соответствуют DIP-1. Вместо реализации тесной связи поддерживается абстракция (протокол) TorqueOperations и TransmissionOperations. Модули нижнего уровня наследуют эти абстракции для соответствия и реализации необходимых функций. Модули высокого уровня принимают аргументы в форме абстракций, что гарантирует, что модули низкого уровня имеют необходимое соответствие для операций. Таким образом, модули высокого уровня не зависят от модулей низкого уровня, и оба они зависят от абстракций!

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

Слева: протокол Engine реализован для выполнения операций двигателя, можно заметить, что до определенной степени Engine зависит от деталей его работы, детали - это внутреннее устройство наших низкоуровневых модулей, иногда это может давать неожиданные результаты. EngineOld и EngineNew не работают так, как можно было бы ожидать. Здесь абстракции зависят от деталей, и это не идеально. Себастьян не хотел бы водить машину, которая не тормозит.

Справа все наоборот. Здесь детали зависят от абстракции, это обеспечивает соответствие необходимым функциям. И EngineOld, и EngineNew адекватно выполняют требуемые операции, здесь детализация зависит от абстракции.

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

Чао!