Карандаш или шариковая ручка — это конкретные объекты, но как тогда назвать пишущий инструмент? Сам по себе он представляет собой абстрактное понятие, которое нельзя точно описать. Но иногда главная идея заключается в том, что можно сделать, а не в том, чем конкретно это делается. Поставленную задачу реализуют интерфейсы (interfaces).
Golang interface
В языке программирования Golang существует тип «интерфейс». Для наилучшего понимания этого типа скажем, что большая часть разных типов данных фокусируется на хранимых ими значениях:
— тип string предназначен для хранения строк;
— тип int, integer — для целых чисел и т. д.
Однако тип interface отличается. Использование интерфейсов сфокусировано не на сохраняемом значении (value), а на том, что именно этот type делает (вспоминаем пример с пишущим инструментом). А так как методы выражают поведение предоставленного типа, интерфейсы объявляются с набором таких методов, которых этот тип должен удовлетворить.
Можно вспомнить interface для записи Writer, который существует в стандартной библиотеке Go. Он позволяет записывать:
— текст;
— сжатые архивы;
— картинки CSV (CSV — comma-separated values — это значения, разделенные запятыми. Формат CSV популярен у работающих с табличными приложениями — Google Sheets, Microsoft Excel).
Также Writer может записывать вывод на экране, файл на диск, ответ на web-запрос и т. п. То есть Writer представлен одним единым интерфейсом, посредством которого можно записать, что угодно. Вывод прост — interface очень гибок, и тот же Writer — простой тому пример.
Ниже объявим переменную типа interface:
Созданная нами переменная t способна хранить значение любого типа, а это удовлетворяет интерфейсу. Говоря точнее, данный тип подойдет интерфейсу, если он объявляет метод talk, который не принимает аргументы, возвращая строку.
Ниже объявляются уже два типа, которые отвечают требованиям:
Пусть martian — это структура (struct) без полей, laser — целое число. Оба типа предоставляют метод talk, а значит, их можно присвоить к t, что и показано ниже:
Меняемая переменная t может принимать форму martian либо laser. Разработчики утверждают, что интерфейсы обладают полиморфизмом, т. е. присутствует возможность менять форму.
В большинстве случаев интерфейсы объявляются в качестве именованных типов, а это уже можно использовать повторно. Есть правило относительно именования типов интерфейса, которое связано с суффиком «–er». К примеру, тот, кто говорит — это talker (talk+er).
Тип интерфейса мы можем применять везде, где используются иные типы. Функция shout ниже имеет параметр talker.
Интерфейсы характеризуются гибкостью и показывают ее, когда надо изменить либо расширить код. Если объявить новый тип с методом talk, функция shout станет с ним работать. То есть любой код, который зависит лишь от интерфейса, может оставаться прежним даже в том случае, если имплементации добавляются либо меняются.
Также interface можно использовать со встраиванием структуры — это особенность языка Go. В коде ниже laser встраивается в starship:
Пример использования
Любой код имплементирует интерфейс, причем даже тот, который уже существует. В программе (program) ниже находится пример реализации вывода выдуманной даты, состоящей из часа дня и дня года.
package main import (
«fmt»
«time»
)
// stardate возвращает выдуманное нами измерение времени для указанной даты
func stardate(t time.Time) float64 {
doy := float64(t.YearDay())
h := float64(t.Hour()) / 24.0
return 1000 + doy + h
}
func main() {
day := time.Date(2012, 8, 6, 5, 17, 0, 0, time.UTC)
fmt.Printf(«%.1f Curiosity has landed\n», stardate(day)) // Выводится: 1219.2 Curiosity has landed
}
Чем полезны интерфейсы?
Существует ряд причин для применения интерфейсов в Go. Вот некоторые наиболее важные:
— уменьшается дублирование кода (сокращается объем шаблонного кода);
— облегчается применение заглушек в модульных тестах вместо реальных объектов;
— так как интерфейсы — это архитектурный инструмент, они помогают «отвязывать» части вашей кодовой базы.
Несколько слов о функции append
Представьте, что у вас есть гараж с коллекционными автомобилями. Вы купили новый автомобиль, но он уже не помещается в гараж. Что делать? Придется купить новый, повышенной вместимости. Так и в программировании.
У любого гаража, как и у любого массива, есть конкретная вместимость. Извлеченная часть массива (срез массива, он же слайс) может сфокусироваться на части массива и расти, пока не будет достигнут предел вместимости. Когда «гараж» заполнится, его можно будет заменить гаражом побольше, а потом выполнить добавление (appending) среза в новый гараж большей вместимости.
Представим, что у нас есть 5 автомобилей. Встроенная в Golang функция append добавляет (appends) новый элемент к срезу:
dwarfs := []string{«BMW», «Audi», «Ford», «Volkswagen», «Cadillac»} dwarfs = append(dwarfs, «Chrysler») fmt.Println(dwarfs) // Вывод будет: [BMW Audi Ford Volkswagen Cadillac Chrysler] |
Однако функция append вариативна. Это значит, что есть возможность передать для добавления сразу несколько элементов:
dwarfs = append(dwarfs, «Buick», «Pontiac», «Lincoln») fmt.Println(dwarfs) // Вывод будет: [BMW Audi Ford Volkswagen Cadillac Chrysler Buick Pontiac Lincoln] |
Таким образом в массив было добавлено (appended) несколько элементов.
Источники:
- https://golangs.org/interface;
- https://golangs.org/array-append-make.
Хотите знать о Go больше? Добро пожаловать на курс в OTUS!