Карандаш или шариковая ручка — это конкретные объекты, но как тогда назвать пишущий инструмент? Сам по себе он представляет собой абстрактное понятие, которое нельзя точно описать. Но иногда главная идея заключается в том, что можно сделать, а не в том, чем конкретно это делается. Поставленную задачу реализуют интерфейсы (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:

Интерфейсы в Golang

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

Ниже объявляются уже два типа, которые отвечают требованиям:

Интерфейсы в Golang

Пусть martian — это структура (struct) без полей, laser — целое число. Оба типа предоставляют метод talk, а значит, их можно присвоить к t, что и показано ниже:

Интерфейсы в Golang

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

В большинстве случаев интерфейсы объявляются в качестве именованных типов, а это уже можно использовать повторно. Есть правило относительно именования типов интерфейса, которое связано с суффиком «–er». К примеру, тот, кто говорит — это talker (talk+er).  

Интерфейсы в Golang

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

Интерфейсы в Golang

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

Также interface можно использовать со встраиванием структуры — это особенность языка Go. В коде ниже laser встраивается в starship:

Интерфейсы в Golang

Пример использования

Любой код имплементирует интерфейс, причем даже тот, который уже существует. В программе (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!