Если вы действительно хотите использовать наследование в 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. Вот несколько советов, которые вдохновили меня на написание этой статьи, которая может утолить вашу жажду получения дополнительных знаний по этой теме: