Уроки модуля (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.
Показать подсказку
Встраивание: напишите ТИП, не указывая ему имя поля.
Решение доступно после 3 попыток
Добавьте строковый метод Greet() в Animal и вызовите его непосредственно в переменной Dog (продвижение метода).
Показать подсказку
Метод, определенный в Animal, повышается до Dog благодаря внедрению.
Решение доступно после 3 попыток
Поддерживает ли Go классическое наследование?
type B struct { /* extends A? */ }Резюме
- Никакого наследования: вы сочиняете с помощью встраивания.
- Встраивание = запись ТИПА без имени поля в структуру.
- Поля и методы встроенного типа повышаются.
- Всегда доступен явный доступ:
outer.Inner.Field. - «Лексическое» переопределение: метод внешнего типа затеняет продвинутый.
- Встраивание ≠ is-a: для полиморфизма используйте интерфейсы.