Ответ 1
Интерфейсы - слишком большая тема, чтобы дать здесь исчерпывающий ответ, но некоторые вещи, которые делают их использование понятным.
Интерфейсы - это инструмент. Независимо от того, используете вы их или нет, это зависит от вас, но они могут сделать код более понятным, коротким, более читаемым, и они могут обеспечить хороший API между пакетами или клиентами (пользователями) и серверами (поставщиками).
Да, вы можете создать свой собственный тип struct
и "привязать" к нему методы, например:
type Cat struct{}
func (c Cat) Say() string { return "meow" }
type Dog struct{}
func (d Dog) Say() string { return "woof" }
func main() {
c := Cat{}
fmt.Println("Cat says:", c.Say())
d := Dog{}
fmt.Println("Dog says:", d.Say())
}
Мы уже можем видеть некоторое повторение в приведенном выше коде: когда заставляем Cat
и Dog
что-то сказать. Можем ли мы обращаться с ними как с одним и тем же видом сущности, как с животным? На самом деле, нет. Конечно, мы можем обрабатывать оба как interface{}
, но если мы это сделаем, мы не сможем вызвать их метод Say()
, потому что значение типа interface{}
не определяет никаких методов.
В обоих вышеупомянутых типах есть некоторое сходство: оба имеют метод Say()
с одинаковой сигнатурой (параметры и типы результатов). Мы можем сделать это с помощью интерфейса:
type Sayer interface {
Say() string
}
Интерфейс содержит только сигнатуры методов, но не их реализацию.
Обратите внимание, что в Go тип неявно реализует интерфейс, если его набор методов является надмножеством интерфейса. Там нет декларации о намерениях. Что это значит? Наши предыдущие типы Cat
и Dog
уже реализовали этот интерфейс Sayer
, даже несмотря на то, что это определение интерфейса даже не существовало, когда мы их писали ранее, и мы не трогали их, чтобы пометить их или что-то в этом роде. Они просто делают.
Интерфейсы определяют поведение. Тип, который реализует интерфейс, означает, что у типа есть все методы, которые интерфейс "предписывает".
Поскольку оба реализуют Sayer
, мы можем обрабатывать оба как значение Sayer
, у них есть это общее. Посмотрите, как мы можем справиться как в единстве:
animals := []Sayer{c, d}
for _, a := range animals {
fmt.Println(reflect.TypeOf(a).Name(), "says:", a.Say())
}
(That reflect part is only to get the type name, don't make much of it as of now.)
Важной частью является то, что мы можем обрабатывать как Cat
, так и Dog
как один и тот же тип (тип интерфейса), и работать с ними/использовать их. Если вы быстро приступили к созданию дополнительных типов с помощью метода Say()
, они могут располагаться рядом с Cat
и Dog
:
type Horse struct{}
func (h Horse) Say() string { return "neigh" }
animals = append(animals, Horse{})
for _, a := range animals {
fmt.Println(reflect.TypeOf(a).Name(), "says:", a.Say())
}
Допустим, вы хотите написать другой код, который работает с этими типами. Вспомогательная функция:
func MakeCatTalk(c Cat) {
fmt.Println("Cat says:", c.Say())
}
Да, вышеуказанная функция работает с Cat
и ни с чем другим. Если вы хотите что-то подобное, вы должны написать это для каждого типа. Нет нужды говорить, насколько это плохо.
Да, вы могли бы написать его, чтобы получить аргумент interface{}
и использовать утверждение типа или переключатели типа, что уменьшит количество вспомогательных функций, но все равно будет выглядеть ужасно.
Решение? Да, интерфейсы. Просто объявите функцию, чтобы получить значение типа интерфейса, который определяет поведение, которое вы хотите с ним делать, и все:
func MakeTalk(s Sayer) {
fmt.Println(reflect.TypeOf(s).Name(), "says:", s.Say())
}
Вы можете вызывать эту функцию со значением Cat
, Dog
, Horse
или любого другого типа, не известного до сих пор, который имеет метод Say()
. Круто.
Попробуйте эти примеры на Go Playground.