Модульні уроки (3/5)
Композиція (вбудовування)
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. Це потрібно коли
є зіткнення.
Перевизначення (часткове): метод "затінення"
Якщо зовнішній тип визначає метод з тим же ім'ям, що й підвищений один, його метод "перемагає". Зсередини завжди можна дістатися до вбудованого один через назву типу:
func (c Dog) Greet() string {
return "BAU! " + c.Animal.Greet()
}Це не поліморфне успадкування: це просто розділення лексичних імен. Для поліморфізму ви використовуєте інтерфейси (Модуль 6).
Багаторазове вбудовування
Ви можете вставити декілька типів:
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):
type ReadWriter interface {
io.Reader
io.Writer
}Це еквівалентно об’єднанню методів Reader та Writer.
Коли використовувати явну композицію (не вбудовування)
Якщо вам потрібне поле з іменем і відсутність автоматичного просування, використовуйте звичайне поле склад:
type Dog struct {
Anim Animal // NORMAL field, no promotion
Breed string
}
c.Anim.Name // you need the field nameВбудовування — це зручний інструмент делегування, а не вибір дизайну суворо краще, ніж явна композиція: використовуйте її, коли справді хочете для просування API внутрішнього типу.
Спробуйте
Визначте Dog, який вбудовує Animal (вбудовування, без назви поля) і має поле рядка Breed.
Показати підказку
Вбудовування: напишіть TYPE без назви поля.
Рішення доступне після 3 спроб
Додайте рядковий метод Greet() до Animal і викликайте його безпосередньо у змінній Dog (просування методу).
Показати підказку
Завдяки вбудовуванню метод, визначений у Animal, змінено на Dog.
Рішення доступне після 3 спроб
Чи підтримує Go класичне успадкування?
type B struct { /* extends A? */ }Підведення підсумків
- Без успадкування: ви створюєте за допомогою вбудовування.
- Вбудовування = запис TYPE без імені поля в структурі.
- Поля та методи вбудованого типу отримують підвищення.
- Явний доступ доступний завжди:
outer.Inner.Field. - «Лексичне» перевизначення: метод зовнішнього типу затінює просунутий.
- Вбудовування ≠ is-a: для поліморфізму використовуйте інтерфейси.