Если вы действительно хотите использовать наследование в Go, тогда встраивание типов максимально приближено к вам. Но есть кое-что, на что следует обратить внимание.

Теория встраивания довольно проста: включив тип в качестве безымянного параметра в другой тип, экспортируемые параметры и методы, определенные для встроенного типа ed, становятся доступными через встраиваемый ing типа. Компилятор решает это с помощью метода, называемого «продвижение»: экспортируемые свойства и методы встроенного типа повышаются до типа встраивания.

Основы

Чтобы продемонстрировать, как использовать встраивание типов, мы начнем с базовой структуры:

type Ball struct {
    Radius   int
    Material string
}

Если вы хотите встроить или «унаследовать» это в новую структуру, вы можете сделать это следующим образом:

type Football struct {
    Ball
}

Итак, теперь вы встроили Ball в Football. Теперь, если вы хотите создать новый футбольный мяч, вы можете сделать это следующим образом:

fb := Football{}
fmt.Printf("fb = %+v\n", fb)

Какие выходы:

fb = {Ball:{Radius:0 Material:}}

Допустим, вы хотите задать параметры мяча футбольного мяча, вам нужно сделать это так:

fb := Football{Ball{Radius: 5, Material: "leather"}}
fmt.Printf("fb = %+v\n", fb)

Теперь, если у вас есть этот метод, определенный для встроенного типа ed, Ball:

func (b Ball) Bounce() {
    fmt.Printf("Bouncing ball %+v\n", b)
}

Вы можете получить доступ к этому методу через встроенный тип, Футбол:

fb.Bounce()

Что производит этот вывод:

Bouncing ball {Radius:5 Material:leather}

Если вы хотите, вы также можете вызвать этот метод через тип встраивания, который доступен как параметр:

fb.Ball.Bounce()

Встраивание интерфейсов

Если встроенный тип реализует определенный интерфейс, то он также доступен через тип встраивания. Вот интерфейс и функция, которая принимает интерфейс в качестве параметра:

type Bouncer interface {
    Bounce()
}
func BounceIt(b Bouncer) {
    b.Bounce()
}

Теперь вы можете вызвать метод, используя тип встраивания:

BounceIt(fb)

Встраивание указателей

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

type Football struct {
    *Ball
}

Так как это указатель, помните о панике с нулевым указателем. В нашем случае создание «пустого» Football с последующим вызовом BounceIt (fb) вызовет панику, потому что определен метод Bounce (). по типу значения. Это можно исправить, обновив его до типа указателя:

func (b *Ball) Bounce() {
    fmt.Printf("Bouncing ball %+v\n", b)
}

Встраивание интерфейсов

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

type Football struct {
    Bouncer
}

Таким же образом можно создать новый футбольный отскок:

fb := Football{&Ball{Radius: 5, Material: "leather"}}
fb.Bounce()

Какие выходы:

Bouncing ball &{Radius:5 Material:leather}

Обратите внимание, что Ball преобразован в указатель не из-за интерфейса, а потому, что мы определили метод Bounce для Ball с помощью приемника указателя в предыдущей главе.

Поскольку Футбол больше не имеет ссылки на Ball, его свойства Радиус и Материал недоступны.

На что следует обратить внимание

Теперь, когда вы знаете основы встраивания, вам следует знать о нескольких подводных камнях. Обратите на них пристальное внимание, потому что, если вы пришли из объектно-ориентированного фона, вы, скорее всего, сделаете эти ошибки.

Встроенная структура не имеет доступа к структуре внедрения.

В отличие от традиционного наследования, встроенная структура (дочерняя) не может получить доступ к чему-либо из вложенной структуры (родительской). Если вы дублируете свойство, его значение никогда не будет доступно «ниже по потоку». Обратите внимание на эту настройку структуры Football и метода Bounce:

type Football struct {
    Ball
    Radius int
}
func (b Ball) Bounce() {
    fmt.Printf("Radius = %d\n", b.Radius)
}

И это присвоение переменной:

fb := Football{Ball{Radius: 5, Material: "leather"}, 7}

Затем эти два вызова метода будут выводить «Радиус = 5»:

fb.Bounce()
fb.Ball.Bounce()

Невозможно привести к типу встраиваемой структуры.

Помните, что встраивание - это не то же самое, что наследование. Кроме того, что вы на самом деле пытаетесь сделать?

Сигнатуры конфликтующих методов не будут компилироваться, если вы их используете

Компилятор не предупредит вас о конфликте сигнатур ваших методов. Компилятор будет жаловаться только тогда, когда вы используете этот метод в любом месте своего кода. Позвольте мне продемонстрировать это поведение с помощью следующих структур:

type Ball struct {
    Radius   int
    Material string
}
type Football struct {
    Ball
}
type Bouncer interface {
    Bounce()
}
// compliant to signature of Bouncer interface
func (b Ball) Bounce() {
    fmt.Printf("Bouncing ball %+v\n", b)
}
// NOT compliant to signature of Bouncer interface
func (fb Football) Bounce(distance int) {
    fmt.Printf("Bouncing football %+v for %d meters\n", fb, 
        distance)
}

И вот как их можно называть.

fb := Football{Ball{Radius: 5, Material: "leather"}}
fb.Bounce(7) // works
fb.Ball.Bounce() // also works
fb.Bounce() // errors "not enough arguments in call to fb.Bounce"
BounceIt(fb) // errors "Football does not implement Bouncer (wrong type for Bounce method)"

Это поведение также применяется, когда вы используете то же имя в типе внедрения для параметра, например:

type Football struct {
    Ball
    Bounce int
}

Опять же, это прекрасно компилируется, но вы будете жаловаться, когда вы попытаетесь вызвать fb.Bounce («Football.Bounce - это поле, а не метод») или вызовите BounceIt (fb) («Football не реализовать Bouncer (отсутствует метод Bounce) »)

Неоднозначные методы не будут компилироваться, если вы их используете

Встраивание типов с использованием неоднозначных методов не всегда очевидно, и поэтому его гораздо труднее обнаружить. Возьмем, к примеру, эту модификацию:

type Football struct {
    Ball
    Bouncer
}

Поскольку Ball уже совместим с интерфейсом Bouncer, это не должно быть проблемой, верно? Ну, нет, если вы хотите использовать его вот так:

BounceIt(fb)

Компилятор отправит вам следующее сообщение:

cannot use fb (type Football) as type Bouncer in argument to BounceIt: Football does not implement Bouncer (missing Bounce method)

Хотя вам в первую очередь кажется, что это не имеет особого смысла, вам следует взглянуть на это с точки зрения компилятора: потому что метод Bounce () продвигается как в Ball и интерфейс Bouncer, он не знает, какой из них использовать. Он просто выбрасывает их обоих. И из-за этого вы получаете сообщение об ошибке «отсутствует метод».

Встраивание в интерфейсы невозможно

Из-за поведенческой природы интерфейсов встраивание типов не может происходить в интерфейсах.

Встраивание интерфейсов по ссылке невозможно

По той же причине, по которой вы не должны определять интерфейс как параметр указателя: это не имеет смысла.

Заключение

Встраивание типов не делает Go объектно-ориентированным, но может быть полезным инструментом в реляционном моделировании.

Это все, что я хотел сказать о встраивании типов в Go. Вот несколько советов, которые вдохновили меня на написание этой статьи, которая может утолить вашу жажду получения дополнительных знаний по этой теме: