Menu
Coddy logo textTech

Продвижение методов

Часть раздела Объектно-ориентированное программирование путешествия по 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 обеспечивает повторное использование поведения без традиционного наследования.

challenge icon

Задание

Легко

Давайте создадим систему музыкального плеера, которая демонстрирует, как методы встроенных типов продвигаются во внешний тип и как это позволяет реализовывать интерфейсы через композицию.

Вы организуете свой код в двух файлах:

  • 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")
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование