Есть ли в Go наследование? Как достигается переиспользование кода и полиморфизм?

Ответ

В Go нет классического наследования, как в языках вроде Java или C++. Вместо этого Go использует композицию через встраивание (embedding).

Встраивание для переиспользования кода

Вы можете встроить одну структуру в другую, и поля и методы встроенной структуры становятся доступными напрямую у внешней структуры. Это обеспечивает переиспользование кода, но не является наследованием.

Пример:

// "Базовый" тип
type Animal struct {
    Name string
}

func (a *Animal) Speak() {
    fmt.Printf("%s издает звукn", a.Name)
}

// "Производный" тип
type Dog struct {
    Animal // Встраивание Animal в Dog
    Breed  string
}

// У Dog есть свой собственный метод
func (d *Dog) Bark() {
    fmt.Printf("%s лает!n", d.Name)
}

func main() {
    d := Dog{
        Animal: Animal{Name: "Рекс"},
        Breed:  "Овчарка",
    }

    d.Speak() // Вызов метода из встроенной структуры Animal
    d.Bark()  // Вызов собственного метода Dog
}

Важное замечание: Если у структуры Dog будет свой метод Speak(), он не переопределит, а затенит метод из Animal. Метод Animal все еще можно будет вызвать явно: d.Animal.Speak().

Интерфейсы для полиморфизма

Полиморфизм в Go достигается с помощью интерфейсов. Интерфейс определяет набор методов. Любой тип, который реализует все методы интерфейса, неявно удовлетворяет этому интерфейсу.

// Интерфейс, описывающий поведение
type Speaker interface {
    Speak()
}

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

func main() {
    dog := &Dog{Animal: Animal{Name: "Рекс"}}
    // cat := &Cat{...} // Предположим, у Cat тоже есть метод Speak()

    MakeSound(dog) // Работает, т.к. *Dog реализует Speak()
    // MakeSound(cat) // Тоже будет работать
}

Итог: Go сознательно отказывается от наследования в пользу более гибкого и ясного подхода: композиция > наследование.