Интерфейсы: определение, неявная реализация и type assertion
Содержание курса
Присваивание конкретных типов переменной интерфейса и диспетчеризация метода
Когда переменная объявлена с интерфейсным типом, ей можно присвоить любое значение, чей конкретный тип реализует этот интерфейс:
var s Stringer
s = Dog{Name: "Rex"}
fmt.Println(s.String()) // Dog: Rex
s = Cat{Name: "Murka"}
fmt.Println(s.String()) // Cat: Murka
При вызове s.String() Go смотрит не на тип переменной s (он всегда Stringer), а на конкретный тип, который в ней хранится прямо сейчас. Это и есть диспетчеризация во время выполнения: каждый раз выбирается метод именно того типа, который лежит внутри.
Главный смысл интерфейса проявляется в функциях:
func printAll(items []Stringer) {
for _, item := range items {
fmt.Println(item.String())
}
}
printAll не знает ничего о Dog и Cat. Она работает с любым значением, у которого есть метод String() string. Передадим оба типа вместе:
func main() {
animals := []Stringer{
Dog{Name: "Rex"},
Cat{Name: "Murka"},
Dog{Name: "Buddy"},
}
printAll(animals)
}
Вывод:
Dog: Rex
Cat: Murka
Dog: Buddy
Каждый вызов item.String() внутри цикла разрешается в метод именно того конкретного типа, который находится в item на этой итерации. Срез []Stringer хранит значения разных типов, и это не требует ни приведения, ни дополнительных проверок — компилятор гарантирует, что у каждого элемента есть нужный метод.
Одна и та же переменная s сначала указывала на Dog, потом на Cat. Это не переопределение типа переменной — тип s всегда Stringer. Меняется только конкретное значение внутри.
