Встраивание
Представьте анкету сотрудника, в которую целиком вклеена личная анкета: имя, фамилия и всё остальное. Поверх неё в анкете сотрудника есть ещё графа «Компания». Отдел кадров читает имя прямо с листа сотрудника и не ищет сначала вклеенную страницу.
Так и в Go. Тип Employee должен уметь всё, что умеет Person, и хранить ещё название компании. Если положить Person в обычное поле p Person, каждое обращение растягивается: e.p.Name, e.p.Greet(). Код работает, но имя p в таких обращениях ничего не добавляет. В Go тип можно встроить в структуру, и тогда его поля и методы доступны напрямую:
package main
import "fmt"
type Person struct{ Name string }
type Employee struct {
Person
Company string
}
func (p Person) Greet() string { return "Hi, " + p.Name }
func main() {
e := Employee{Person: Person{Name: "Ann"}, Company: "Abyss"}
fmt.Println(e.Name, e.Greet()) // Ann Hi, Ann
}Давайте разберём пример по строкам:
- В
Employeeпервым идёт поле без имени — только типPerson. - Литерал заполняет вклеенную анкету значением
Person{Name: "Ann"}, а графуCompany— строкой"Abyss". e.Nameчитает имя прямо изe.e.Greet()вызывает методPerson, и программа печатаетAnn Hi, Ann.
Встроенное поле
Поле, у которого указан только тип, без имени, называют встроенным, а сам приём — встраиванием. Поля и методы встроенного типа продвигаются во внешнюю структуру:
- поле:
e.Nameозначаетe.Person.Name; - метод с получателем-значением:
e.Greet()означаетe.Person.Greet(); - метод с получателем-указателем: если бы
Greetпринимал*Person, вызовe.Greet()у переменнойeтоже бы работал.
Встроенное поле — часть структуры, как любое другое поле. Имя у него тоже есть, и оно совпадает с именем его типа. Отсюда два следствия:
- в литерале пишут
Person: Person{Name: "Ann"}; - значение
Personцеликом достают какe.Person.
Если встроенное поле не указать в литерале, оно получит нулевое значение: в Employee{Company: "Abyss"} поле Name пустое.
Затенение имён
Своё поле внешней структуры с тем же именем затеняет продвинутое. Так же свой метод Greet закрыл бы продвинутый. Встроенное поле при этом остаётся доступным через имя типа:
type Manager struct {
Person
Name string
}m := Manager{Person: Person{Name: "Ann"}, Name: "Boss"}
fmt.Println(m.Name, m.Person.Name, m.Greet()) // Boss Ann Hi, AnnДавайте разберём, что печатает вторая строка:
m.Name— своё полеManager, поэтомуBoss.m.Person.Name— поле встроенногоPerson, поэтомуAnn.m.Greet()возвращаетHi, Ann: методGreetобъявлен уPersonи видит только поляPerson.
Метод Greet не знает о поле Name типа Manager. Вклеенная анкета не догадывается, что лежит внутри другой.
Это не наследование
В языках с наследованием тип-наследник сам считается базовым типом и подставляется вместо него. Встраивание так не работает: Employee содержит значение Person, но сам типом Person не становится. Метод встроенного типа всегда работает со своим значением и никогда не вызывает методы внешнего типа.
Этот вопрос часто задают на собеседовании: «Встраивание в Go — это наследование?» Ответ — нет:
- внешняя структура содержит внутреннюю как обычное поле;
- поля и методы внутренней доступны по короткому пути;
Employeeне становится типомPersonи не подставляется вместо него.
Здесь кончается и картинка с анкетой: отдел кадров примет лист сотрудника за личную анкету, а компилятор — нет.
Частая ошибка
Employee передают туда, где ждут Person:
func greet(p Person) string { return "Hello, " + p.Name }
func main() {
e := Employee{Person: Person{Name: "Ann"}, Company: "Abyss"}
fmt.Println(greet(e)) // ✗
}cannot use e (variable of struct type Employee) as Person value in argument to greetПрограмма не компилируется: Employee и Person — разные типы, даже если один встроен в другой. Передайте встроенное поле явно: greet(e.Person).
Главное
- Встроенное поле — поле без имени, только тип; его поля и методы продвигаются во внешнюю структуру.
- Своё поле или метод с тем же именем затеняет продвинутый, а встроенное значение доступно как
e.Person. - Встраивание — не наследование:
Employeeне становится типомPersonи не передаётся вместо него.
Подпишись, чтобы пройти курс
Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.