Полиморфизм через интерфейсы
Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy. Урок 41 из 107.
Polymorphism позволяет единообразно обращаться с разными типами через общий interface. В Go это достигается исключительно с помощью interface, без наследования или иерархий классов.
Когда function accepts тип interface в качестве параметра, в него можно передать любой конкретный тип, который реализует этот interface. function не нужно знать конкретный тип. Её интересует только поведение, определённое interface:
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.
Этот подход делает ваш код гибким и расширяемым. Добавление нового типа, который издаёт звуки, не требует изменений в существующих функциях: просто реализуйте interface, и всё заработает автоматически.
Задание
ЛегкоДавайте создадим систему описания транспортных средств, демонстрирующую полиморфизм в действии. Вы создадите разные типы транспортных средств, которые используют общее поведение через интерфейс, а затем напишете одну функцию, работающую с любым транспортным средством.
Вы организуете код в двух файлах:
vehicles.go: Define интерфейсDescriber, требующий наличия methodDescribe() string. Затем создайте три типа транспортных средств, каждый из которых по-своему Implement этот интерфейс:Carс полямиBrandиModel. ЕгоDescribe()возвращаетCar: [Brand] [Model]Motorcycleс полямиBrandиEngineCC(int). ЕгоDescribe()возвращаетMotorcycle: [Brand] [EngineCC]ccBicycleс полемType(например,MountainилиRoad). ЕгоDescribe()возвращаетBicycle: [Type]
main.go: Создайте function с именемPrintDescription, которая accepts любойDescriberи выводит результат вызоваDescribe(). Считайте details транспортных средств из input, создайте по одному экземпляру каждого типа транспортного средства и передайте каждый из них вPrintDescription, чтобы продемонстрировать, что одна и та же function работает со всеми тремя разными типами.
Будут предоставлены следующие входные данные:
- Строка 1: марка Car
- Строка 2: Model Car
- Строка 3: марка Motorcycle
- Строка 4: объём двигателя Motorcycle в CC (целое число)
- Строка 5: тип Bicycle
Например, если заданы Toyota, Camry, Honda, 600 и Mountain, результат должен быть следующим:
Car: Toyota Camry
Motorcycle: Honda 600cc
Bicycle: MountainОбратите внимание: PrintDescription не нужно знать, получает ли она Car, Motorcycle или Bicycle. Она просто вызывает Describe(), и каждый тип отвечает своим уникальным результатом. Это и есть полиморфизм: одна function, несколько вариантов поведения.
Попробуйте сами
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")
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
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Потренируйтесь самостоятельно: Онлайн-компилятор Go