Интерфейсы
В Go интерфейс — абстрактный тип, определяющий набор сигнатур методов без реализации. Основная концепция интерфейса — описание поведения, а конкретная реализация предоставляется типами, реализующими интерфейс. Интерфейсы в Go широко используются для реализации полиморфизма, слабой связанности и переиспользования кода.
Концепция
История развития интерфейсов в Go имеет водораздел. В Go 1.17 и ранее официальное определение интерфейса в справочнике: набор методов.
An interface type specifies a method set called its interface.
Определение реализации интерфейса:
A variable of interface type can store a value of any type with a method set that is any superset of the interface. Such a type is said to implement the interface
Перевод: когда набор методов типа является надмножеством набора методов интерфейса, и значение этого типа может храниться в переменной типа интерфейса, то говорят, что тип реализует интерфейс.
Однако в Go 1.18 определение интерфейса изменилось: интерфейс определяется как набор типов.
An interface type defines a type set.
Определение реализации интерфейса:
A variable of interface type can store a value of any type that is in the type set of the interface. Such a type is said to implement the interface
Перевод: когда тип находится в наборе типов интерфейса, и значение этого типа может храниться в переменной типа интерфейса, то говорят, что тип реализует интерфейс. Также дано дополнительное определение:
Тип T реализует интерфейс I, когда:
- T не является интерфейсом и является элементом набора типов интерфейса I
- T является интерфейсом, и набор типов T является подмножеством набора типов интерфейса I
Если T реализует интерфейс, то значение T также реализует этот интерфейс.
Главное изменение в Go 1.18 — добавление дженериков. Новое определение интерфейса служит дженерикам, но не влияет на использование интерфейсов ранее. Интерфейсы делятся на два типа:
- Базовый интерфейс (Basic Interface): интерфейс, содержащий только набор методов
- Универсальный интерфейс (General Interface): интерфейс, содержащий набор типов
Набор методов — это набор методов, набор типов — набор типов.
TIP
Возможно, эта концепция покажется сложной, но на практике не нужно глубоко понимать всё вышеизложенное.
Базовые интерфейсы
Базовый интерфейс — набор методов.
Объявление
Пример интерфейса:
type Person interface {
Say(string) string
Walk(int)
}Это интерфейс Person с двумя открытыми методами Walk и Say. В интерфейсах имена параметров функций не важны, но можно добавить имена параметров и возвращаемых значений.
Инициализация
Интерфейс нельзя инициализировать, так как это только спецификация без реализации, но можно объявить:
func main() {
var person Person
fmt.Println(person)
}Вывод:
<nil>Реализация
Пример: строительная компания хочет специальный кран, предоставляет спецификацию и чертежи, указывая, что кран должен поднимать и перемещать грузы. Компания не производит краны, только предоставляет спецификацию — это интерфейс. Компания A принимает заказ, создаёт уникальный кран по собственной технологии и передаёт компании. Компании не важны технология и уникальность, главное — подъём и перемещение грузов. Предоставление конкретной функции по спецификации — это реализация. Использование функции по спецификации интерфейса, скрывая внутреннюю реализацию — это программирование, ориентированное на интерфейс. Через некоторое время уникальный кран сломался, компания A закрылась. Компания B создала более мощный кран по спецификации. Благодаря одинаковой функции подъёма и перемещения, новый кран бесшовно заменил старый, не влияя на прогресс. Изменение внутренней реализации при сохранении функции, возможность замены — преимущество программирования, ориентированного на интерфейс.
Описание на Go:
// Интерфейс крана
type Crane interface {
JackUp() string
Hoist() string
}
// Кран A
type CraneA struct {
work int // внутренние поля различаются
}
func (c CraneA) Work() {
fmt.Println("Использование технологии A")
}
func (c CraneA) JackUp() string {
c.Work()
return "jackup"
}
func (c CraneA) Hoist() string {
c.Work()
return "hoist"
}
// Кран B
type CraneB struct {
boot string
}
func (c CraneB) Boot() {
fmt.Println("Использование технологии B")
}
func (c CraneB) JackUp() string {
c.Boot()
return "jackup"
}
func (c CraneB) Hoist() string {
c.Boot()
return "hoist"
}
type ConstructionCompany struct {
Crane Crane // хранение крана типа Crane
}
func (c *ConstructionCompany) Build() {
fmt.Println(c.Crane.JackUp())
fmt.Println(c.Crane.Hoist())
fmt.Println("Здание построено")
}
func main() {
// Использование крана A
company := ConstructionCompany{CraneA{}}
company.Build()
fmt.Println()
// Замена на кран B
company.Crane = CraneB{}
company.Build()
}Вывод:
Использование технологии A
jackup
Использование технологии A
hoist
Здание построено
Использование технологии B
jackup
Использование технологии B
hoist
Здание построеноВ примере реализация интерфейса неявная, что соответствует определению базового интерфейса: набор методов типа является надмножеством набора методов интерфейса. В Go для реализации интерфейса не требуется ключевое слово implements — достаточно реализовать все методы интерфейса. После реализации можно инициализировать интерфейс. В структуре ConstructionCompany объявлена переменная типа Crane, хранящая все значения, реализующие интерфейс Crane. Поскольку переменная типа Crane, доступны только методы JackUp и Hoist, другие методы (Work, Boot) недоступны.
Любой пользовательский тип может иметь методы, следовательно, любой пользовательский тип может реализовать интерфейс. Примеры:
type Person interface {
Say(string) string
Walk(int)
}
type Man interface {
Exercise()
Person
}Набор методов интерфейса Man — надмножество Person, поэтому Man реализует Person, что похоже на "наследование".
type Number int
func (n Number) Say(s string) string {
return "bibibibibi"
}
func (n Number) Walk(i int) {
fmt.Println("can not walk")
}Базовый тип Number — int. Хотя это странно в других языках, набор методов Number — надмножество Person, поэтому это реализация.
type Func func()
func (f Func) Say(s string) string {
f()
return "bibibibibi"
}
func (f Func) Walk(i int) {
f()
fmt.Println("can not walk")
}
func main() {
var function Func
function = func() {
fmt.Println("do somthing")
}
function()
}Типы функций также могут реализовывать интерфейсы.
Пустой интерфейс
type Any interface{
}Интерфейс Any не содержит методов. По определению реализации, все типы реализуют Any, так как набор методов всех типов — надмножество пустого множества. Поэтому Any может хранить значения любого типа.
func main() {
var anything Any
anything = 1
println(anything)
fmt.Println(anything)
anything = "something"
println(anything)
fmt.Println(anything)
anything = complex(1, 2)
println(anything)
fmt.Println(anything)
anything = 1.2
println(anything)
fmt.Println(anything)
anything = []int{}
println(anything)
fmt.Println(anything)
anything = map[string]int{}
println(anything)
fmt.Println(anything)
}Вывод:
(0xe63580,0xeb8b08)
1
(0xe63d80,0xeb8c48)
something
(0xe62ac0,0xeb8c58)
(1+2i)
(0xe62e00,0xeb8b00)
1.2
(0xe61a00,0xc0000080d8)
[]
(0xe69720,0xc00007a7b0)
map[]Результаты двух выводов различаются. Интерфейс можно представить как кортеж (val, type), где type — конкретный тип, при вызове метода используется конкретное значение типа.
interface{}Это анонимный пустой интерфейс. В разработке анонимный пустой интерфейс используется для обозначения приёма значений любого типа:
func main() {
DoSomething(map[int]string{})
}
func DoSomething(anything interface{}) interface{} {
return anything
}В последующих обновлениях официальное предложение — использовать any вместо interface{}. Они эквивалентны, так как any — псевдоним типа:
type any = interface{}При сравнении пустых интерфейсов сравниваются базовые типы. Если типы не совпадают — false, затем сравниваются значения:
func main() {
var a interface{}
var b interface{}
a = 1
b = "1"
fmt.Println(a == b)
a = 1
b = 1
fmt.Println(a == b)
}Вывод:
false
trueЕсли базовый тип несравним, возникнет panic. Встроенные типы Go и их сравнимость:
| Тип | Сравним | Критерий |
|---|---|---|
| Числовые | Да | Равенство значений |
| Строки | Да | Равенство значений |
| Массивы | Да | Равенство всех элементов |
| Срезы | Нет | Несравнимы |
| Структуры | Да | Равенство всех полей |
| map | Нет | Несравнимы |
| Каналы | Да | Равенство адресов |
| Указатели | Да | Равенство адресов |
| Интерфейсы | Да | Равенство хранимых данных |
В Go есть специальный интерфейс для всех сравнимых типов — comparable:
type comparable interface{ comparable }TIP
Попытка сравнения несравнимых типов вызовет panic.
Универсальные интерфейсы
Универсальные интерфейсы служат дженерикам. После освоения дженериков станут понятны универсальные интерфейсы. Подробнее: Дженерики
