Menu
Coddy logo textTech

Полиморфизм через интерфейсы

Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy — урок 41 из 107.

Полиморфизм позволяет единообразно обрабатывать различные типы через общий интерфейс. В Go это достигается исключительно с помощью интерфейсов, без наследования или иерархий классов.

Когда функция принимает тип интерфейса в качестве параметра, в неё можно передать любой конкретный тип, реализующий этот интерфейс. Функции не нужно знать конкретный тип — её интересует только поведение, определённое интерфейсом:

type Speaker interface {
    Speak() string
}

type Dog struct{ Name string }
func (d Dog) Speak() string { return "Woof!" }

type Cat struct{ Name string }
func (c Cat) Speak() string { return "Meow!" }

func MakeSound(s Speaker) {
    fmt.Println(s.Speak())
}

Теперь MakeSound работает с любым типом, у которого есть метод Speak():

func main() {
    dog := Dog{Name: "Rex"}
    cat := Cat{Name: "Whiskers"}
    
    MakeSound(dog)  // Гав!
    MakeSound(cat)  // Мяу!
}

Один и тот же вызов функции приводит к разному поведению в зависимости от фактически переданного типа. Это полиморфизм в действии. Функция MakeSound написана один раз, но работает с неограниченным количеством типов, если они удовлетворяют интерфейсу Speaker.

Этот подход делает ваш код гибким и расширяемым. Добавление нового типа, который «говорит», не требует изменений в существующих функциях — достаточно реализовать интерфейс, и всё заработает автоматически.

challenge icon

Задание

Легко

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

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

  • vehicles.go: Определите интерфейс Describer, который требует метод Describe() string. Затем создайте три типа транспортных средств, каждый из которых реализует этот интерфейс по-своему:
    • Car с полями Brand и Model — его метод Describe() возвращает "Car: [Brand] [Model]"
    • Motorcycle с полями Brand и EngineCC (int) — его метод Describe() возвращает "Motorcycle: [Brand] [EngineCC]cc"
    • Bicycle с полем Type (например, "Mountain" или "Road") — его метод Describe() возвращает "Bicycle: [Type]"
  • main.go: Создайте функцию с именем PrintDescription, которая принимает любой Describer и выводит результат вызова Describe(). Считайте данные о транспортных средствах из входного потока, создайте по одному экземпляру каждого типа и передайте каждый из них в PrintDescription, чтобы продемонстрировать, что одна и та же функция работает со всеми тремя различными типами.

Будут предоставлены следующие входные данные:

  • Строка 1: Марка автомобиля (Car brand)
  • Строка 2: Модель автомобиля (Car model)
  • Строка 3: Марка мотоцикла (Motorcycle brand)
  • Строка 4: Объем двигателя мотоцикла (Motorcycle engine CC, целое число)
  • Строка 5: Тип велосипеда (Bicycle type)

Например, для входных данных Toyota, Camry, Honda, 600 и Mountain ваш вывод должен быть следующим:

Car: Toyota Camry
Motorcycle: Honda 600cc
Bicycle: Mountain

Обратите внимание, что функции PrintDescription не нужно знать, получает ли она Car, Motorcycle или Bicycle — она просто вызывает Describe(), и каждый тип отвечает своим уникальным выводом. Это и есть полиморфизм: одна функция, несколько вариантов поведения.

Шпаргалка

Полиморфизм позволяет единообразно работать с различными типами через общий интерфейс. В Go это достигается с помощью интерфейсов без использования наследования или иерархий классов.

Когда функция принимает тип интерфейса в качестве параметра, в неё можно передать любой конкретный тип, реализующий этот интерфейс:

type Speaker interface {
    Speak() string
}

type Dog struct{ Name string }
func (d Dog) Speak() string { return "Woof!" }

type Cat struct{ Name string }
func (c Cat) Speak() string { return "Meow!" }

func MakeSound(s Speaker) {
    fmt.Println(s.Speak())
}

Одна и та же функция работает с разными типами:

func main() {
    dog := Dog{Name: "Rex"}
    cat := Cat{Name: "Whiskers"}
    
    MakeSound(dog)  // Woof!
    MakeSound(cat)  // Meow!
}

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

Попробуйте сами

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

// TODO: Создайте функцию с именем PrintDescription, которая принимает любой Describer
// и выводит результат вызова Describe()

func main() {
	reader := bufio.NewReader(os.Stdin)

	// Чтение данных автомобиля
	carBrand, _ := reader.ReadString('\n')
	carBrand = strings.TrimSpace(carBrand)
	carModel, _ := reader.ReadString('\n')
	carModel = strings.TrimSpace(carModel)

	// Чтение данных мотоцикла
	motoBrand, _ := reader.ReadString('\n')
	motoBrand = strings.TrimSpace(motoBrand)
	motoEngineStr, _ := reader.ReadString('\n')
	motoEngineStr = strings.TrimSpace(motoEngineStr)
	motoEngine, _ := strconv.Atoi(motoEngineStr)

	// Чтение данных велосипеда
	bicycleType, _ := reader.ReadString('\n')
	bicycleType = strings.TrimSpace(bicycleType)

	// TODO: Создайте Car, Motorcycle и Bicycle, используя входные значения

	// TODO: Вызовите PrintDescription для каждого транспортного средства, чтобы продемонстрировать полиморфизм
	fmt.Println("TODO: Print vehicle descriptions")
}
quiz iconПроверьте себя

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

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