Утиная типизация в 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.
Задание
ЛегкоДавайте создадим систему обмена сообщениями, демонстрирующую работу утиной типизации. Вы создадите полностью несвязанные типы, которые могут отправлять сообщения через общий интерфейс, при этом ни один из них явно не объявляет, что реализует его.
Вы организуете код в трёх файлах:
messengers.go: Создайте три несвязанных структуры, которые случайно обладают одинаковым поведением:PhoneсNumberfield. ЕгоSendMessage(text string) stringmethod возвращаетSMS to [Number]: [text]ComputerсEmailfield. ЕгоSendMessage(text string) stringmethod возвращаетEmail to [Email]: [text]PigeonсNamefield. ЕгоSendMessage(text string) stringmethod возвращает[Name] carries: [text]
sender.go: Определите интерфейсMessenger, который требует methodSendMessage(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
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
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