Skip to content

Интерфейсы

В 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

Возможно, эта концепция покажется сложной, но на практике не нужно глубоко понимать всё вышеизложенное.

Базовые интерфейсы

Базовый интерфейс — набор методов.

Объявление

Пример интерфейса:

go
type Person interface {
  Say(string) string
  Walk(int)
}

Это интерфейс Person с двумя открытыми методами Walk и Say. В интерфейсах имена параметров функций не важны, но можно добавить имена параметров и возвращаемых значений.

Инициализация

Интерфейс нельзя инициализировать, так как это только спецификация без реализации, но можно объявить:

go
func main() {
   var person Person
   fmt.Println(person)
}

Вывод:

 <nil>

Реализация

Пример: строительная компания хочет специальный кран, предоставляет спецификацию и чертежи, указывая, что кран должен поднимать и перемещать грузы. Компания не производит краны, только предоставляет спецификацию — это интерфейс. Компания A принимает заказ, создаёт уникальный кран по собственной технологии и передаёт компании. Компании не важны технология и уникальность, главное — подъём и перемещение грузов. Предоставление конкретной функции по спецификации — это реализация. Использование функции по спецификации интерфейса, скрывая внутреннюю реализацию — это программирование, ориентированное на интерфейс. Через некоторое время уникальный кран сломался, компания A закрылась. Компания B создала более мощный кран по спецификации. Благодаря одинаковой функции подъёма и перемещения, новый кран бесшовно заменил старый, не влияя на прогресс. Изменение внутренней реализации при сохранении функции, возможность замены — преимущество программирования, ориентированного на интерфейс.

Описание на Go:

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) недоступны.

Любой пользовательский тип может иметь методы, следовательно, любой пользовательский тип может реализовать интерфейс. Примеры:

go
type Person interface {
   Say(string) string
   Walk(int)
}

type Man interface {
   Exercise()
   Person
}

Набор методов интерфейса Man — надмножество Person, поэтому Man реализует Person, что похоже на "наследование".

go
type Number int

func (n Number) Say(s string) string {
  return "bibibibibi"
}

func (n Number) Walk(i int) {
  fmt.Println("can not walk")
}

Базовый тип Numberint. Хотя это странно в других языках, набор методов Number — надмножество Person, поэтому это реализация.

go
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()
}

Типы функций также могут реализовывать интерфейсы.

Пустой интерфейс

go
type Any interface{

}

Интерфейс Any не содержит методов. По определению реализации, все типы реализуют Any, так как набор методов всех типов — надмножество пустого множества. Поэтому Any может хранить значения любого типа.

go
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 — конкретный тип, при вызове метода используется конкретное значение типа.

go
interface{}

Это анонимный пустой интерфейс. В разработке анонимный пустой интерфейс используется для обозначения приёма значений любого типа:

go
func main() {
   DoSomething(map[int]string{})
}

func DoSomething(anything interface{}) interface{} {
   return anything
}

В последующих обновлениях официальное предложение — использовать any вместо interface{}. Они эквивалентны, так как any — псевдоним типа:

go
type any = interface{}

При сравнении пустых интерфейсов сравниваются базовые типы. Если типы не совпадают — false, затем сравниваются значения:

go
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:

go
type comparable interface{ comparable }

TIP

Попытка сравнения несравнимых типов вызовет panic.

Универсальные интерфейсы

Универсальные интерфейсы служат дженерикам. После освоения дженериков станут понятны универсальные интерфейсы. Подробнее: Дженерики

Golang by www.golangdev.cn edit