Перейти до основного вмісту
Модуль 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.

Завантаження редактора…
Показати підказку

Вбудовування: напишіть TYPE без назви поля.

Рішення доступне після 3 спроб

вправи#go.m5.l3.e2
Спроби: 0Завантаження…

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

Завантаження редактора…
Показати підказку

Завдяки вбудовуванню метод, визначений у Animal, змінено на Dog.

Рішення доступне після 3 спроб

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

Чи підтримує Go класичне успадкування?

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

Підведення підсумків

  • Без успадкування: ви створюєте за допомогою вбудовування.
  • Вбудовування = запис TYPE без імені поля в структурі.
  • Поля та методи вбудованого типу отримують підвищення.
  • Явний доступ доступний завжди: outer.Inner.Field.
  • «Лексичне» перевизначення: метод зовнішнього типу затінює просунутий.
  • Вбудовування ≠ is-a: для поліморфізму використовуйте інтерфейси.