Как в Go реализован полиморфизм и что такое «утиная типизация»?

Ответ

Полиморфизм в Go реализуется через интерфейсы. Интерфейс определяет набор методов, но не их реализацию. Любой тип, который реализует все методы интерфейса, неявно (автоматически) удовлетворяет этому интерфейсу.

Этот подход называется "утиная типизация" (duck typing), что описывается фразой: "Если нечто ходит как утка и крякает как утка, то это утка". В Go это означает: если тип имеет все необходимые методы, он может быть использован там, где ожидается данный интерфейс, без явного указания implements.

Пример:

import "fmt"

// Определяем интерфейс
type Speaker interface {
    Speak() string
}

// Тип Dog реализует интерфейс Speaker
type Dog struct{}
func (d Dog) Speak() string {
    return "Woof!"
}

// Тип Cat реализует интерфейс Speaker
type Cat struct{}
func (c Cat) Speak() string {
    return "Meow!"
}

// Функция, которая полиморфно работает с любым Speaker
func MakeSound(s Speaker) {
    fmt.Println(s.Speak())
}

func main() {
    d := Dog{}
    c := Cat{}

    MakeSound(d) // Выведет: Woof!
    MakeSound(c) // Выведет: Meow!
}

Ключевые аспекты полиморфизма в Go:

  1. Неявная реализация: Типы удовлетворяют интерфейсам автоматически. Это способствует слабой связанности (loose coupling) кода, так как пакеты могут определять и использовать интерфейсы, не зная о конкретных реализациях друг друга.
  2. Пустой интерфейс (interface{} или any): Интерфейс без методов. Поскольку любой тип имеет ноль или более методов, любой тип удовлетворяет пустому интерфейсу. Это позволяет создавать функции, принимающие значения любого типа. С Go 1.18 any является псевдонимом для interface{}.
  3. Значение интерфейса: Переменная типа интерфейс внутри состоит из двух компонентов: указателя на информацию о конкретном типе и указателя на данные этого типа. Это позволяет рантайму вызывать правильный метод в рантайме (динамическая диспетчеризация).