Наследование vs Встраивание в Go. В чем ключевые различия?

Ответ

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

Встраивание (Embedding)

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

import "fmt"

type Animal struct {
    Name string
}

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

type Dog struct {
    Animal // Встраивание структуры Animal
    Breed string
}

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

    // Метод Speak() "поднялся" на уровень Dog
    d.Speak() // Вывод: Рекс издает звук животного

    // Поле Name также доступно напрямую
    fmt.Println(d.Name) // Вывод: Рекс
}

Ключевые отличия от наследования


  1. Отношение "is a" vs "has a": Наследование моделирует отношение "является" (Собака является Животным). Встраивание — это отношение "имеет" (Собака имеет свойства Животного). Это композиция.



  2. Отсутствие иерархии типов: Dog не является подтипом Animal. Переменной типа Animal нельзя присвоить значение типа Dog. Для полиморфизма в Go используются интерфейсы.



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



  4. Явный доступ: Всегда можно обратиться к полям и методам встроенной структуры напрямую через ее тип: d.Animal.Name.