Go
Интерфейсы
Поиск…
замечания
Интерфейсы в Go - это только фиксированные наборы методов. Тип неявно реализует интерфейс, если его набор методов является надмножеством интерфейса. Нет декларации о намерениях.
Простой интерфейс
В Go интерфейс - это всего лишь набор методов. Мы используем интерфейс для указания поведения данного объекта.
type Painter interface {
Paint()
}
Тип реализации не должен заявлять, что он реализует интерфейс. Достаточно определить методы одной и той же сигнатуры.
type Rembrandt struct{}
func (r Rembrandt) Paint() {
// use a lot of canvas here
}
Теперь мы можем использовать структуру как интерфейс.
var p Painter
p = Rembrandt{}
Интерфейс может быть удовлетворен (или реализован) произвольным числом типов. Также тип может реализовать произвольное количество интерфейсов.
type Singer interface {
Sing()
}
type Writer interface {
Write()
}
type Human struct{}
func (h *Human) Sing() {
fmt.Println("singing")
}
func (h *Human) Write() {
fmt.Println("writing")
}
type OnlySinger struct{}
func (o *OnlySinger) Sing() {
fmt.Println("singing")
}
Здесь структура Human
удовлетворяет как интерфейсу Singer
и Writer
, но структура OnlySinger
удовлетворяет только интерфейсу Singer
.
Пустой интерфейс
Существует пустой тип интерфейса, который не содержит методов. Мы объявляем его как interface{}
. Это не содержит методов, которые удовлетворяют каждому type
. Следовательно, пустой интерфейс может содержать любое значение типа.
var a interface{}
var i int = 5
s := "Hello world"
type StructType struct {
i, j int
k string
}
// all are valid statements
a = i
a = s
a = &StructType{1, 2, "hello"}
Наиболее распространенным вариантом использования интерфейсов является обеспечение того, чтобы переменная поддерживала одно или несколько типов поведения. Напротив, основным вариантом использования для пустого интерфейса является определение переменной, которая может содержать любое значение независимо от его конкретного типа.
Чтобы вернуть эти значения в качестве их первоначальных типов, нам просто нужно сделать
i = a.(int)
s = a.(string)
m := a.(*StructType)
или же
i, ok := a.(int)
s, ok := a.(string)
m, ok := a.(*StructType)
ok
указывает, ok
ли interface a
заданному типу. Если это невозможно, то ok
будет false
.
Значения интерфейса
Если вы объявляете переменную интерфейса, она может хранить любой тип значения, который реализует методы, объявленные интерфейсом!
Если мы объявим h
interface Singer
, он может сохранить значение типа Human
или OnlySinger.
Это связано с тем, что все они реализуют методы, указанные интерфейсом Singer
.
var h Singer
h = &human{}
h.Sing()
Определение базового типа из интерфейса
В go иногда бывает полезно узнать, какой базовый тип вы прошли. Это можно сделать с помощью переключателя типа. Это предполагает, что мы имеем две структуры:
type Rembrandt struct{} func (r Rembrandt) Paint() {} type Picasso struct{} func (r Picasso) Paint() {}
Это реализует интерфейс Painter:
type Painter interface { Paint() }
Затем мы можем использовать этот переключатель для определения базового типа:
func WhichPainter(painter Painter) { switch painter.(type) { case Rembrandt: fmt.Println("The underlying type is Rembrandt") case Picasso: fmt.Println("The underlying type is Picasso") default: fmt.Println("Unknown type") } }
Проверка времени компиляции, если тип удовлетворяет интерфейсу
Интерфейсы и реализации (типы, реализующие интерфейс) «отсоединены». Таким образом, это законный вопрос, как проверить во время компиляции, если тип реализует интерфейс.
Один из способов попросить компилятор проверить, что тип T
реализует интерфейс I
, пытаясь выполнить присвоение, используя нулевое значение для T
или указатель на T
, если это необходимо. И мы можем назначить пустой идентификатор, чтобы избежать ненужного мусора:
type T struct{}
var _ I = T{} // Verify that T implements I.
var _ I = (*T)(nil) // Verify that *T implements I.
Если T
или *T
не реализует I
, это будет ошибка времени компиляции.
Этот вопрос также появляется в официальных FAQ: как я могу гарантировать, что мой тип удовлетворяет интерфейсу?
Переключатель типа
Переключатели типов также могут использоваться для получения переменной, соответствующей типу корпуса:
func convint(v interface{}) (int,error) {
switch u := v.(type) {
case int:
return u, nil
case float64:
return int(u), nil
case string:
return strconv(u)
default:
return 0, errors.New("Unsupported type")
}
}
Утверждение типа
Вы можете получить доступ к реальному типу данных интерфейса с помощью Type Assertion.
interfaceVariable.(DataType)
Пример структуры MyType
реализующей интерфейс Subber
:
package main
import (
"fmt"
)
type Subber interface {
Sub(a, b int) int
}
type MyType struct {
Msg string
}
//Implement method Sub(a,b int) int
func (m *MyType) Sub(a, b int) int {
m.Msg = "SUB!!!"
return a - b;
}
func main() {
var interfaceVar Subber = &MyType{}
fmt.Println(interfaceVar.Sub(6,5))
fmt.Println(interfaceVar.(*MyType).Msg)
}
Без .(*MyType)
мы не смогли бы получить доступ к Msg
Field. Если мы попробуем interfaceVar.Msg
он покажет ошибку компиляции:
interfaceVar.Msg undefined (type Subber has no field or method Msg)
Переходные интерфейсы из математического аспекта
В математике, особенно в Set Theory , у нас есть коллекция вещей, которая называется множеством, и мы называем эти вещи элементами . Мы показываем набор с его именем, подобным A, B, C, ... или явно с помещением его члена в обозначение скобок: {a, b, c, d, e}. Предположим, что мы имеем произвольный элемент x и множество Z. Ключевой вопрос: «Как мы можем понять, что x является членом Z или нет?». Математик отвечает на этот вопрос понятием: характеристическое свойство множества. Характеристическое свойство множества - это выражение, полностью описывающее множество. Например, мы имеем множество натуральных чисел, которые являются {0, 1, 2, 3, 4, 5, ...}. Мы можем описать это множество следующим выражением: {a n | a 0 = 0, a n = a n-1 +1}. В последнем выражении a 0 = 0, a n = a n-1 +1 является характеристическим свойством множества натуральных чисел. Если у нас есть это выражение, мы можем полностью построить этот набор . Таким образом, опишем множество четных чисел . Мы знаем, что это множество производится этими числами: {0, 2, 4, 6, 8, 10, ...}. С первого взгляда мы понимаем, что все эти числа также являются натуральным числом , другими словами, если добавить некоторые дополнительные условия к характеристическому свойству натуральных чисел, мы можем построить новое выражение, описывающее этот набор . Поэтому мы можем описать это выражение: {n | n является членом натуральных чисел, а напоминание n на 2 равно нулю}. Теперь мы можем создать фильтр, который получает характеристическое свойство набора и фильтрует некоторые желаемые элементы для возврата элементов нашего набора. Например, если у нас есть фильтр с натуральным числом, как натуральные числа, так и четные числа могут передавать этот фильтр, но если у нас есть фильтр с четным числом, то некоторые элементы, такие как 3 и 137871, не могут пройти фильтр.
Определение интерфейса в Go похоже на определение свойства характеристики и механизм использования интерфейса в качестве аргумента функции подобно фильтру, который обнаруживает, что элемент является частью нашего желаемого набора или нет. Давайте рассмотрим этот аспект с кодом:
type Number interface {
IsNumber() bool // the implementation filter "meysam" from 3.14, 2 and 3
}
type NaturalNumber interface {
Number
IsNaturalNumber() bool // the implementation filter 3.14 from 2 and 3
}
type EvenNumber interface {
NaturalNumber
IsEvenNumber() bool // the implementation filter 3 from 2
}
Характеристическим свойством Number
являются все структуры, которые имеют метод IsNumber
, для NaturalNumber
- все те, которые имеют IsNumber
и IsNaturalNumber
и, наконец, для EvenNumber
- все типы, которые имеют IsNumber
, IsNaturalNumber
и IsEvenNumber
. Благодаря этой интерпретации интерфейса легко понять, что, поскольку interface{}
не имеет какого-либо характеристического свойства, принимаем все типы (потому что у него нет фильтра для различения значений).