Menu
Coddy logo textTech

Утиная типизация в Go

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

Термин утиная типизация происходит от высказывания: «Если оно ходит как утка и крякает как утка, значит, это утка». В Go это означает, что типу не нужно явно объявлять, что он реализует интерфейс. Ему достаточно иметь нужные методы.

Рассмотрим этот интерфейс и два совершенно несвязанных типа:

type Quacker interface {
    Quack() string
}

type Duck struct{}
func (d Duck) Quack() string { return "Quack!" }

type Robot struct{}
func (r Robot) Quack() string { return "Beep-quack!" }

Ни Duck, ни Robot нигде в своих определениях не упоминают Quacker. Тем не менее оба удовлетворяют интерфейсу, просто потому что у них есть метод Quack() с правильной сигнатурой:

func MakeItQuack(q Quacker) {
    fmt.Println(q.Quack())
}

func main() {
    MakeItQuack(Duck{})   // Кря!
    MakeItQuack(Robot{})  // Бип-кря!
}

Это неявное удовлетворение является мощным инструментом, поскольку позволяет определять интерфейсы после того, как типы уже существуют. Вы можете создать интерфейс, которому соответствуют типы из внешних пакетов, не изменяя их исходный код. Типам не нужно знать о вашем интерфейсе. Они просто должны вести себя правильно.

Duck typing в Go предоставляет гибкость динамических языков, сохраняя безопасность типов во время компиляции. Компилятор проверяет, что типы действительно имеют требуемые method, прежде чем разрешить использовать их как значения interface.

challenge icon

Задание

Легко

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

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

  • messengers.go: Создайте три несвязанных структуры, которые случайно обладают одинаковым поведением:
    • Phone с Number field. Его SendMessage(text string) string method возвращает SMS to [Number]: [text]
    • Computer с Email field. Его SendMessage(text string) string method возвращает Email to [Email]: [text]
    • Pigeon с Name field. Его SendMessage(text string) string method возвращает [Name] carries: [text]
    Обратите внимание, что ни один из этих типов не упоминает какой-либо интерфейс. Просто так получилось, что у них одинаковая сигнатура method.
  • sender.go: Определите интерфейс Messenger, который требует method SendMessage(text string) string. Создайте функцию с именем Broadcast, которая принимает срез Messenger и строку сообщения, а затем возвращает срез строк, содержащий результат вызова SendMessage у каждого messenger.
  • main.go: Считайте данные из входных данных, создайте по одному экземпляру каждого типа messenger, соберите их в срез Messenger и используйте Broadcast, чтобы отправить сообщение через все эти объекты. Выведите каждый результат в отдельной строке.

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

  • Line 1: номер телефона
  • Line 2: адрес электронной почты
  • Line 3: имя голубя
  • Line 4: сообщение для broadcast

Например, если даны 555-1234, alice@mail.com, Winston и Hello World, ваш вывод должен быть таким:

SMS to 555-1234: Hello World
Email to alice@mail.com: Hello World
Winston carries: Hello World

Главная идея заключается в том, что у Phone, Computer и Pigeon нет ничего общего. У них нет общего базового типа, и они явно не реализуют Messenger. Однако, поскольку все они одинаково "quack" (имеют соответствующий method), Go позволяет использовать их как значения Messenger. Так работает утиная типизация.

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

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	
	// Прочитать номер телефона
	scanner.Scan()
	phoneNumber := scanner.Text()
	
	// Read email address
	scanner.Scan()
	email := scanner.Text()
	
	// Прочитать имя голубя
	scanner.Scan()
	pigeonName := scanner.Text()
	
	// Прочитать сообщение для рассылки
	scanner.Scan()
	message := scanner.Text()
	
	// TODO: Создать по одному экземпляру каждого типа мессенджера (Phone, Computer, Pigeon)
	
	// TODO: Собрать всех мессенджеров в слайс Messenger
	
	// TODO: Использовать Broadcast для отправки сообщения через всех мессенджеров
	
	// TODO: Вывести каждый результат на своей строке
	
	// Заглушка для использования переменных (удалить при реализации)
	_ = phoneNumber
	_ = email
	_ = pigeonName
	_ = message
}
quiz iconПроверьте себя

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

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

Потренируйтесь самостоятельно: Онлайн-компилятор Go