Что такое ТВЕРДЫЙ?

Взяв за основу книгу Роберта С. Мартина «Чистая архитектура», мы можем сказать следующее:

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

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

  • Допускать изменения
  • Легко понять
  • Являются основой компонентов, которые могут использоваться во многих программных системах.

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

  • Единственная ответственность:

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

  • Принцип открытия-закрытия:

"Артефакт программного обеспечения должен быть открыт для расширения, но закрыт для модификации"

  • Принцип замены Лисков

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

  • Разделение интерфейсов

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

  • Инверсия зависимостей

«Модули высокого уровня не должны зависеть от модулей низкого уровня. Оба должны зависеть от абстракций. Абстракции не должны зависеть от деталей. Детали не должны зависеть от абстракций»

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

Примечание. Для получения дополнительной информации о SOLID я рекомендую прочитать книгу «Чистая архитектура» Роберта С. Мартина.

ТВЕРДЫЙ и вперед

Как мы видели в предыдущем разделе, SOLID предназначен для объектно-ориентированного программирования, но это не является ограничением для таких языков, как Golang, и я покажу вам, как вы можете упорядочить его и реализовать в своих проектах Golang.

Давайте начнем с каждого из принципов и определим, как они реализованы в Golang.

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

— Определение: «У модуля должна быть одна и только одна причина для изменения»

Мы уже знаем, что означает этот принцип, теперь пришло время узнать, как реализовать его в Golang.

Для golang мы могли бы определить этот принцип как «Одна функция или тип должны иметь одну и только одну работу и одну и только одну ответственность»,сообщая, что пришло время перейти к примеру, чтобы увидеть это в действие:

Наличие следующего кода:

Как видим, совсем неплохо, но что происходит в коде? Если мы остановимся на мгновение и прочитаем строки 14 и 23 внутри методов области, мы можем кое-что обнаружить, код вычисляет площадь и печатает результат, он нарушает «принцип единой ответственности».

Теперь, применяя «принцип единой ответственности»:

  • Шаг 1. Измените методы, чтобы они выполняли только одно действие, в данном случае вычисление площади

  • Шаг 2. Мы можем определить новый тип для обработки вывода области, в нашем примере мы хотим распечатать его на консоли. Затем мы делегируем преобразование строки новому типу и методу с именем outPrinter, у которого есть метод для возврата строки с областью формы.

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

  • Шаг 3. Наконец, мы можем сделать следующее:

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

Принцип открытия-закрытия — OCP

— Определение: "Артефакт программного обеспечения должен быть открыт для расширения, но закрыт для модификации"

Давайте начнем со следующего кода:

В предыдущем коде не реализован принцип «открыть-закрыть», причина в следующем:

  • Тип калькулятора имеет метод «sumAreas», который определяет в качестве параметра «фигуры» типа interface{}. В следующих строках мы можем найти случай переключения для сопоставления каждого возможного типа, что нарушает принцип, потому что во время определения новой фигуры «треугольник» на примере нам нужно будет изменить метод «sumAreas» для обработки нового типа.

Как это исправить?

Мы можем следовать следующему подходу:

  • Интерфейс «форма» определяется областью методов.
  • Каждый из наших примеров типов определяет метод «область».

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

А вот как использовать новую реализацию:

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

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

В Golang нет наследования, но есть композиция. мы можем составить несколько структур, и это будет работать для нашего примера «принципа подстановки Лисков». Перейдем к примеру кода:

В приведенном выше коде мы можем проанализировать следующее:

  • Код определяет тип «транспортное средство», который определяет метод интерфейса «getName».
  • «Автомобиль» и «мотоцикл» имеют доступ к «транспортному средству» с помощью композиции, что означает, что помимо доступа к свойствам, автомобиль и мотоцикл имеют доступ к методам «транспортное средство».
  • Метод принтера будет работать с транспортными средствами, автомобилями и мотоциклами.

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

В следующем коде мы видим основную функцию и вывод консоли.

Разделение интерфейсов — Интернет-провайдер

— Определение: «Клиентов нельзя принуждать полагаться на методы, которые они не используют»

Прост как его определение, и мы можем сказать: «Держите интерфейсы простыми, предпочтительнее всего один метод».

В следующем примере я покажу вам, как применить этот принцип в Golang:

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

Теперь, как мы можем видеть на изображении ниже, код определяет две фигуры, один квадрат и один куб, каждая из которых реализует методы area() и volume().

И здесь у нас есть две функции: одна для суммирования площадей, а другая для суммирования объемов.

В коде все выглядит хорошо, но если немного проанализировать и вспомнить принцип «Клиентов нельзя заставлять зависеть от методов, которые они не используют», то в фигурах можно увидеть следующее:

  • Квадрат не нуждается в методе объема, потому что это плоская форма.
  • Только куб нуждается в методе объема, потому что это форма объекта

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

  • Добавьте новый интерфейс с именем «shape», чтобы определить метод «area()».
  • Добавьте новый интерфейс для фигур «объекты», чтобы определить метод «volume()», и он состоит из интерфейса «shape», позволяющего использовать метод «area()».

В нашем примере Square реализует «area()», а куб реализует «area()» и «volume()», которые охватывают «принцип разделения интерфейсов», потому что мы разделяем интерфейсы, чтобы не заставлять их использовать или определять метод. который не будет использоваться главой, в данном случае квадратом.

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

Инверсия зависимости - DIP

— Определение: «Модули высокого уровня не должны зависеть от модулей низкого уровня. Оба должны зависеть от абстракций. Абстракции не должны зависеть от деталей. Детали не должны зависеть от абстракций»

Вот пример, чтобы объяснить больше, что означает принцип.

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

Код не соответствует принципу, потому что определяет в репозитории тип структуры для доступа к методам запроса.

Теперь, реализуя принцип «инверсии зависимостей», код модифицируется в следующих пунктах:

  • Добавлен новый интерфейс для определения методов базы данных.
  • Модифицированный репозиторий, вместо определения свойства типа «MySQL» теперь определяет абстракцию, которая является типом интерфейса.

На изображении ниже мы видим изменения, примененные на основе определения принципа:

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

Заключение

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

Рекомендуемые лекции:

  • Чистая архитектура Роберта С. Мартина
  • Шаблоны проектирования Эриха Гаммы