Продвижение методов
Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy — урок 36 из 107.
Точно так же, как встраивание структур продвигает поля во внешнюю структуру, Go также продвигает методы из встраиваемых типов. Это означает, что вы можете вызывать методы, определенные во встроенной структуре, непосредственно у внешней структуры.
type Engine struct {
Horsepower int
}
func (e Engine) Start() string {
return "Engine started"
}
type Car struct {
Model string
Engine // встроенный
}
Так как Car встраивает Engine, метод Start() продвигается автоматически:
func main() {
c := Car{
Model: "Sedan",
Engine: Engine{Horsepower: 200},
}
fmt.Println(c.Start()) // Двигатель запущен
fmt.Println(c.Engine.Start()) // также работает
}
Это продвижение имеет важное значение для интерфейсов. Если встраиваемый тип удовлетворяет интерфейсу, внешний тип также автоматически ему удовлетворяет:
type Starter interface {
Start() string
}
func Ignite(s Starter) {
fmt.Println(s.Start())
}
func main() {
c := Car{Model: "Sedan", Engine: Engine{Horsepower: 200}}
Ignite(c) // Car удовлетворяет Starter через Engine
}
Тип Car никогда явно не реализует Starter, но при этом удовлетворяет интерфейсу, поскольку его встроенный Engine имеет необходимый метод. Именно так Go обеспечивает повторное использование поведения без традиционного наследования.
Задание
ЛегкоДавайте создадим систему музыкального плеера, которая демонстрирует, как методы встроенных типов продвигаются во внешний тип и как это позволяет реализовывать интерфейсы через композицию.
Вы организуете свой код в двух файлах:
audio.go: Создайте основные блоки для вашего музыкального плеера:- Структуру
AudioPlayerс полемBrand(string) и методомPlay() string, который возвращает[Brand] playing audio - Интерфейс
Playable, требующий методPlay() string - Структуру
Smartphoneс полемModel, которая встраиваетAudioPlayer - Структуру
Tabletс полемModel, которая встраиваетAudioPlayer
- Структуру
main.go: Создайте функцию с именемStartPlayback, которая принимает любойPlayableи возвращает результат вызоваPlay(). Считайте информацию об устройствах из входных данных, создайте Smartphone и Tablet и продемонстрируйте, что оба они удовлетворяют интерфейсуPlayableчерез встроенныйAudioPlayer— даже если ни один из типов явно не реализуетPlay()сам по себе.
Будут предоставлены следующие входные данные:
- Строка 1: Название модели смартфона
- Строка 2: Бренд аудиосистемы смартфона
- Строка 3: Название модели планшета
- Строка 4: Бренд аудиосистемы планшета
Для каждого устройства выведите его модель, а затем результат передачи этого устройства в функцию StartPlayback.
Например, для входных данных iPhone 15, Apple Audio, iPad Pro и Beats ваш вывод должен быть следующим:
iPhone 15
Apple Audio playing audio
iPad Pro
Beats playing audioОбратите внимание, что и Smartphone, и Tablet могут быть переданы в StartPlayback, потому что метод Play() из AudioPlayer автоматически продвигается в каждый внешний тип, удовлетворяя интерфейсу Playable без какого-либо дополнительного кода.
Шпаргалка
Когда структура встраивает другой тип, методы встроенного типа автоматически переносятся во внешнюю структуру:
type Engine struct {
Horsepower int
}
func (e Engine) Start() string {
return "Engine started"
}
type Car struct {
Model string
Engine // встроено
}
func main() {
c := Car{
Model: "Sedan",
Engine: Engine{Horsepower: 200},
}
fmt.Println(c.Start()) // Engine started
fmt.Println(c.Engine.Start()) // также работает
}
Если встроенный тип удовлетворяет интерфейсу, внешний тип также автоматически ему удовлетворяет:
type Starter interface {
Start() string
}
func Ignite(s Starter) {
fmt.Println(s.Start())
}
func main() {
c := Car{Model: "Sedan", Engine: Engine{Horsepower: 200}}
Ignite(c) // Car удовлетворяет Starter через Engine
}
Это позволяет повторно использовать поведение через композицию без традиционного наследования.
Попробуйте сами
package main
import (
"bufio"
"fmt"
"os"
)
// TODO: Создайте функцию StartPlayback, которая принимает любой Playable
// и возвращает результат вызова Play()
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Чтение модели смартфона
scanner.Scan()
smartphoneModel := scanner.Text()
// Чтение аудио-бренда смартфона
scanner.Scan()
smartphoneBrand := scanner.Text()
// Чтение модели планшета
scanner.Scan()
tabletModel := scanner.Text()
// Чтение аудио-бренда планшета
scanner.Scan()
tabletBrand := scanner.Text()
// TODO: Создайте Smartphone со встроенным AudioPlayer
// TODO: Создайте Tablet со встроенным AudioPlayer
// TODO: Выведите модель смартфона, затем вызовите StartPlayback со смартфоном
// TODO: Выведите модель планшета, затем вызовите StartPlayback с планшетом
_ = smartphoneModel
_ = smartphoneBrand
_ = tabletModel
_ = tabletBrand
fmt.Println("TODO: Complete the implementation")
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП в Go
Внешние файлыРабочее пространство и модули GoПакеты и импортыЭкспортируемые и неэкспортируемые именаВведение в ООП в GoСтруктуры как классыОпределение методов структурПолучатели-указатели и получатели-значенияИнициализация структурФункции-конструкторыИтоги — Простой калькулятор4Интерфейсы
Введение в интерфейсыНеявная реализацияИнтерфейс как контрактПустой интерфейс (any)Утверждение типаПереключатель типовКомпозиция интерфейсовИнтерфейсы Stringer и ErrorПовторение: Калькулятор фигур7Инкапсуляция
Экспортируемые и неэкспортируемые поляИнкапсуляция на уровне пакетовГеттеры и сеттерыСокрытие информации в GoИтоги — Записи о студентах10Обобщения (Generics) (Go 1.18+)
Введение в GenericsПараметры типовОграничения типовОбобщенные структурыОбходной путь для обобщенных методовИтоги — Обобщенная коллекция13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн Abstract FactoryПаттерн ObserverПаттерн Strategy2Глубокое погружение в типы и структуры
Базовые и составные типыОпределение пользовательских типовТеги структурАнонимные структурыВложенные структурыНулевые значения и значения по умолчаниюПовторение — Контактная книга5Композиция вместо наследования
Почему в Go нет наследованияОсновы встраивания структурПродвижение методовВстраивание нескольких структурВстраивание против агрегацииЗатенение встроенных методовИтоги — Иерархия сотрудников8Обработка ошибок и ООП
Интерфейс errorПользовательские типы ошибокОбертывание ошибок (fmt.Errorf)Sentinel-ошибкиerrors.Is() и errors.As()Panic, Defer и RecoverИтоги — Парсер файлов11Стандартная библиотека и ООП
io.Reader и io.Writersort.InterfaceИнтерфейс fmt.Stringerencoding/json со структурамиИнтерфейс http.HandlerПовторение: модели REST API14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикMiddleware как Декоратор3Указатели и память
Основы указателей в GoУказатели на структурыПередача по значению и по ссылкеФункция new()Сборка мусора в GoПовторение: Конструктор связного списка6Полиморфизм в Go
Полиморфизм через интерфейсыУтиная типизация в GoПравила реализации интерфейсовПолиморфные коллекцииВнедрение зависимостейИтоги — Обработчик платежей9Конкурентность и ООП
Основы горутинКаналы и взаимодействиеБуферизованные и небуферизованные каналыОператор selectsync.Mutex и sync.RWMutexsync.WaitGroupПроектирование потокобезопасных структурПовторение — Worker Pool