Часть 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