Часть 1: Понимание концепции интерфейса.

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

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

Используйте свой реальный опыт для понимания кода

Рисунок 1

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

  • 1. Этот символ понятен всем и, следовательно, сокращает кривую обучения работе с оборудованием.
  • 2. Существует глобальный заговор, в котором нажатие на кнопку увеличивает силу тайного царства людей-ящериц, обитающих под земной корой, потому что символ представляет Бога, которому они поклоняются.

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

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

Что такое интерфейс Go?

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

Листинг 1

type Appliance interface {
   On() error
   Off() error
}

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

Листинг 2

type Laptop struct {
 Power bool
}

func (l Laptop) On() error {
 if !l.Power {
  return errors.New("Laptop needs power")
 }
 return nil
}

func (l Laptop) Off() error {
 return nil
}

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

Листинг 3

type WashingMachine struct {
 HasClothes bool
}

func (w WashingMachine) On() error {
 if !w.HasClothes {
  return errors.New("Washing machine needs clothes loaded first")
 }
 return nil
}

func (w WashingMachine) Off() error {
 return nil
}

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

Тестирование кода

Листинг 4

func main() {
 var a Appliance

 a = Laptop{}
 fmt.Println("Turning laptop on:", a.On())

 a = WashingMachine{}
 fmt.Println("Turning washing machine on:", a.On())
}

В листинге 4 показан пример попытки включить ноутбук и стиральную машину. Обратите внимание, как я повторно использую одну и ту же переменную a; это возможно, потому что обе структуры Laptop и WashingMachine реализуют и удовлетворяют интерфейсу Appliance.

Листинг 5

Turning laptop on: Laptop needs power
Turning washing machine on: Washing machine needs clothes loaded first

Program exited.

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

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

Заключение

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

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

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

Вы можете поиграть с кодом, описанным в этом посте, здесь: https://go.dev/play/p/O8A0r68_2OW