Есть ли в 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 сознательно отказывается от наследования в пользу более гибкого и ясного подхода: композиция > наследование.

Ответ 18+ 🔞

Ну, слушай, тут такая история с Го, как с этим самым наследованием. Всё не как у нормальных людей, блядь! В Java или C++ — там всё понятно, отцов-классов натыкал и поехал. А тут, сука, принципиально иначе.

Вместо наследования у них, блядь, встраивание (embedding). Это как если бы ты не рожал наследника, а просто взял и засунул одного чувака в карман к другому. И теперь второй чувак может доставать из кармана первого его штуки и пользоваться, как своими. Ну, типа композиция, только на максималках.

Смотри, как это выглядит:

// Это типа наш "родитель", но не совсем
type Animal struct {
    Name string
}

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

// А это наш "потомок", который просто засунул Animal себе внутрь
type Dog struct {
    Animal // Вот это самое встраивание! Animal теперь в кармане у Dog
    Breed  string
}

// И у Dog свой личный метод есть
func (d *Dog) Bark() {
    fmt.Printf("%s лает!n", d.Name) // Name-то он из кармана Animal достал!
}

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

    d.Speak() // И охуеть, работает! Метод Speak как будто свой.
    d.Bark()  // И свой метод тоже работает.
}

Но вот тут, блядь, важный момент, в рот меня чих-пых! Если ты в Dog напишешь свой метод Speak(), то он не переопределит старый, а просто затенит его. Старый-то метод всё ещё живёт в кармане, и к нему можно достучаться по полному адресу: d.Animal.Speak(). Никакой магии, всё честно.

А как же полиморфизм, ёпта?

А вот для этого у них отдельная, блядь, песня — интерфейсы. Это вообще гениальная хуйня. Ты описываешь набор действий (методов), и любой тип, который умеет эти действия делать, автоматически подходит под описание. Никаких explicit implements, нихуя!

// Говорим: "Всё, что умеет Speak() — это Speaker"
type Speaker interface {
    Speak()
}

// И вот эта функция готова работать с кем угодно, кто Speaker
func MakeSound(s Speaker) {
    s.Speak()
}

func main() {
    dog := &Dog{Animal: Animal{Name: "Рекс"}}
    // cat := &Cat{...} // Допустим, и кошка умеет Speak()

    MakeSound(dog) // Всё заебись, *Dog умеет Speak(), значит он Speaker
    // MakeSound(cat) // И кошка прокатит!
}

Короче, суть в чём: Го специально не даёт тебе классического наследования, чтобы ты не выстраивал эти ёбанные башни из слоновой кости в 10 этажей. Вместо этого — бери и компонуй из готовых кусков, как из лего. Композиция > наследования, вот их девиз. Сначала мозг ломает, а потом понимаешь — а оно и к лучшему, блядь.