Перейти к основному содержимому
Модуль 5 · Урок 3 из 523/50 в курсе~12 min
Уроки модуля (3/5)

Композиция (вложение)

Go не имеет наследования. Идиоматический ответ на повторное использование: композиция и, в частности, встраивание: включая один тип внутри другого, не давая ему имени поля. Направления и методы встроенный тип продвигается и становится доступным напрямую, как если бы они принадлежали к внешнему типу.

Встраивание структуры

Go
type Animal struct {
    Name string
}

func (a Animal) Greet() string {
    return "ciao da " + a.Name
}

type Dog struct {
    Animal        // embedding — NO field name
    Breed string
}

c := Dog{
    Animal: Animal{Name: "Rex"},
    Breed:  "Pastore",
}

fmt.Println(c.Name)         // "Rex" — promoted from Animal
fmt.Println(c.Greet())      // "ciao da Rex" — promoted method
fmt.Println(c.Breed)        // own field

Встроенное поле по-прежнему доступно по имени типа: c.Animal.Name работает и идентично c.Name. Это необходимо, когда есть столкновения.

Переопределить (частично): метод «затенение»

Если внешний тип определяет метод с тем же именем, что и расширенный тип во-первых, его метод «побеждает». Изнутри вы всегда можете добраться до встроенного один через имя типа:

Go
func (c Dog) Greet() string {
    return "BAU! " + c.Animal.Greet()
}

Это не полиморфное наследование: это просто разрешение лексических имен. Для полиморфизма вы используете интерфейсы (Модуль 6).

Множественное встраивание

Вы можете встроить несколько типов:

Go
type Logger struct{ /*...*/ }
func (l Logger) Log(msg string) { /*...*/ }

type Timer struct{ /*...*/ }
func (t Timer) Now() time.Time { /*...*/ }

type Service struct {
    Logger
    Timer
    Name string
}

s := Service{Name: "api"}
s.Log("start")   // promoted from Logger
s.Now()          // promoted from Timer

Если два встроенных типа имеют метод с ОДНИМ именем, прямой доступ становится неоднозначным, и компилятору требуется явная форма (s.Logger.Foo() или s.Timer.Foo()).

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

Интерфейсы также могут быть «встроены» (см. Модуль 6):

Go
type ReadWriter interface {
    io.Reader
    io.Writer
}

Это эквивалентно объединению методов Reader и Writer.

Когда использовать явную композицию (не встраивание)

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

Go
type Dog struct {
    Anim  Animal    // NORMAL field, no promotion
    Breed string
}
c.Anim.Name   // you need the field name

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

Попробуйте это

Упражнение#go.m5.l3.e1
Попыток: 0Загрузка…

Определите Dog, который встраивает Animal (встраивание, без имени поля) и имеет строковое поле Breed.

Загрузка редактора…
Показать подсказку

Встраивание: напишите ТИП, не указывая ему имя поля.

Решение доступно после 3 попыток

Упражнение#go.m5.l3.e2
Попыток: 0Загрузка…

Добавьте строковый метод Greet() в Animal и вызовите его непосредственно в переменной Dog (продвижение метода).

Загрузка редактора…
Показать подсказку

Метод, определенный в Animal, повышается до Dog благодаря внедрению.

Решение доступно после 3 попыток

Quiz#go.m5.l3.e3
Готово

Поддерживает ли Go классическое наследование?

Go
type B struct { /* extends A? */ }
Варианты ответа

Резюме

  • Никакого наследования: вы сочиняете с помощью встраивания.
  • Встраивание = запись ТИПА без имени поля в структуру.
  • Поля и методы встроенного типа повышаются.
  • Всегда доступен явный доступ: outer.Inner.Field.
  • «Лексическое» переопределение: метод внешнего типа затеняет продвинутый.
  • Встраивание ≠ is-a: для полиморфизма используйте интерфейсы.