После изучения данного урока вы сможете:

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

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

Форум Гоферов

Мы работаем над форумом для программистов на Golang. Очень нужны модераторы которые хотят помочь с ответами для новичков и помочь в развитии Go-сообщества.

Go на Форум

Уроки, статьи и Видео

Мы публикуем в паблике ВК и Telegram качественные обучающие материалы для быстрого изучения Go. Подпишитесь на нас в ВК и в Telegram. Поддержите сообщество Go программистов.

Go в ВК Go в Telegram

Содержание статьи

В стандартной библиотеке Go есть интерфейс для записи. Он называется Writer, с его помощью можно записывать текст, картинки, разделенные запятыми значения (CSV), сжатые архивы и многое другое. Можно выполнить запись вывода на экране, файла на диск или ответ на веб-запрос. С помощью единого интерфейса, Go может записать все что угодно, заняв любое количество памяти. Writer очень гибок.

Шариковая ручка 0,5 мм с синими чернилами — это конкретный объект, тогда как пишущий инструмент — не совсем четкая идея. С помощью интерфейсов код может выражать такие абстрактные понятия, как пишущий инструмент. Подумайте о том, что именно можно сделать, а не о том, что оно из себя представляет. Данный способ мышления через интерфейсы поможет коду адаптироваться к изменениям.

Осмотритесь вокруг, какие конкретные вещи находятся рядом? Что с ними можно сделать? Можно ли сделать то же самое с другими объектами? Есть ли у них общее поведение или интерфейс?

Тип interface в Golang

Большинство типов фокусируется на значениях, что они хранят: integer для целых чисел, string для текста и так далее. Тип interface отличается. Интерфейсы сконцентрированы на том, что тип делает, а не на сохраняемых им значениях.

Методы выражают поведение предоставленного типа, поэтому интерфейсы объявляются с набором методов, которых тип должен удовлетворить. В следующем примере объявляется переменная с типом interface:

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

В следующем примере объявляются два типа, что соответствуют требованиям:

Хотя martian является структурой без полей и laser является целым числом, оба типа предоставляют метод talk, следовательно могут быть присвоены к t, как показано в следующем примере:

Изменяемая переменная t способна принимать форму martian или laser. Программисты говорят, что интерфейсы обладают полиморфизмом, то есть возможность менять форму.

На заметку: В отличие от Java, в Go martian и laser напрямую не объявляют, что они имплементируют интерфейс. О преимуществах поговорим далее в уроке.

Обычно интерфейсы объявляются как именованные типы, что можно повторно использовать. Существуют правило в отношении именования типов интерфейса с суффиком -er: к примеру, talker, то есть тот, кто говорит (talk+er), как показано в следующем примере:

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

Вы можете использовать функцию shout с любым значением, что удовлетворяет требования интерфейса talker, будь то martian или laser, как показано в следующем примере.

Аргумент, который вы передаете функции shout, должен удовлетворять требования интерфейса talker. К примеру, тип crater не удовлетворяет интерфейс talker, поэтому компилятор Go откажется компилировать программу:

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

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

Когда говорит космический корабль, за него говорит лазер. Внедрение laser предоставляет структуре starship метод talk, что встраивается в laser. Теперь космический корабль starship также удовлетворяет требования интерфейса talker, позволяя ему использоваться вместе с shout.

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

Билл Веннерс, JavaWorld

Вопросы для проверки: 

  1. Измените метод talk лазера из Листинга 4 для предотвращения стрельбы из марсианского оружия, чтобы можно было спасти человечество во время вторжения.
  2. Расширьте Листинг 4, объявив новый тип rover c методом talk, что возвращает «whir whir». Используйте функцию shout с вашим новым типом.

Примеры использования интерфейсов в коде на Golang

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

В следующем листинге выводится выдуманная дата, что состоит из дня года и часа дня.

Функция stardate из Листинга 8 ограничивается земными датами. Чтобы исправить это, следующий листинг объявляет интерфейс, который может использовать stardate но уже по своему усмотрению:

Новая функция stardate в Листинге 9 продолжает оперировать земными датами, потому что тип time.Time из стандартной библиотеке удовлетворяет требования интерфейса stardater. Интерфейсы в Go являются удовлетворяемыми косвенным образом, что особенно полезно при работе с чужим кодом.

На заметку: В языках вроде Java это использовать не получилось бы, потому что для java.time понадобилось бы в открытую указать на implements stardater.

С находящимся на месте интерфейсом stardater Листинг 9 может быть расширен с типом sol, что удовлетворяет требования интерфейса с методами YearDay и Hour, как показано в следующем коде.

Функция stardate оперирует как земными датами, так и марсианскими днями, как показано в следующем листинге.

Вопрос для проверки: 

В чем преимущество неявно удовлетворяемых интерфейсов?

Удовлетворение требований интерфейса в Go

Стандартная библиотека Go экспортирует некоторое число интерфейсов с одним методом, которых можно имплементировать в коде.

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

Роб Пайк,
Go от Google: Проектирование языка в
Службе Программной Инженерии

К примеру, пакет fmt объявляет интерфейс Stringer следующим образом:

Если тип предоставляет метод String то Println, Sprintf и другие функции отображения содержимого будут используют его. В следующем листинге метод String нужен для контроля над тем, как пакет fmt отображает место расположения.

Вдобавок к fmt.Stringer популярные интерфейсы стандартной библиотеки включают io.Reader, io.Writer и json.Marshaler.

Интерфейс io.ReadWriter  предоставляет пример внедрения интерфейса, что похож на встраивание структуры. Однако, в отличие от структуры, у интерфейсов нет полей или прикрепленных методов, поэтому встраивание интерфейса может сэкономит время, в противном случае затрачиваемое на набор текста.

Вопрос для проверки: 

Напишите метод String для типа coordinate и используйте его для отображения координат в более читабельном формате.

Ваша программа должна выводить: Elysium Planitia is at 4°30'0.0" N, 135°54'0.0" E

Заключение

  • Типы интерфейса уточняют запрашиваемое поведение с помощью набора методов;
  • Интерфейсы удовлетворяются косвенным образом новым или уже существующим кодом в любом пакете;
  • Структура удовлетворяет интерфейс, которая удовлетворяют внедренные типы;
  • Следуйте примеру стандартной библиотеки и стремитесь сохранить интерфейсы небольшими.

Итоговое задание для проверки: 

Напишите программу, что выводит координаты в формате JSON, расширяя работу, сделанную для предварительной быстрой проверки. JSON вывод должен предоставить все координаты в десятичных градусах (DD), а также в градусах, минутах и секундах:

Этого можно добиться без модификации структуры координат через удовлетворение интерфейса json.Marshaler для настройки JSON. Написанный вами метод MarshalJSON может использовать json.Marshal.

Обратите внимание, что для вычислений в десятичных градусах вам понадобится метод decimal, обсуждаемый в уроке о структурах и методах.