Обходной путь для обобщенных методов
Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy. Урок 71 из 107.
У Go есть заметное ограничение: нельзя определять методы со своими параметрами типа у структуры. Хотя generic-структуры отлично работают, добавление method, который introduces новый parameter типа, не allowed.
Этот код не скомпилируется:
type Box[T any] struct {
Value T
}
// ERROR: метод не должен иметь параметров типа
func (b Box[T]) Convert[U any](fn func(T) U) U {
return fn(b.Value)
}Обходной путь — использовать отдельную generic-функцию вместо method:
type Box[T any] struct {
Value T
}
// Обычный метод — использует параметр типа структуры
func (b Box[T]) Get() T {
return b.Value
}
// Отдельная функция — может иметь свои собственные параметры типа
func Convert[T, U any](b Box[T], fn func(T) U) U {
return fn(b.Value)
}
func main() {
intBox := Box[int]{Value: 42}
// Используем метод
fmt.Println(intBox.Get()) // 42
// Используем отдельную функцию
strResult := Convert(intBox, func(n int) string {
return fmt.Sprintf("Number: %d", n)
})
fmt.Println(strResult) // Number: 42
}Методы generic-структур по-прежнему могут использовать параметр типа структуры T. Ограничение применяется только к введению дополнительных параметров типа в сигнатуре метода.
Когда вам нужна такая гибкость, обобщённая функция, принимающая структуру в качестве своего первого аргумента, даёт тот же результат.
Задание
ЛегкоДавайте создадим набор инструментов для преобразования данных, демонстрирующий обходное решение ограничения Go на generic-методы! Поскольку методы не могут вводить новые Parameters типов, вы создадите отдельные generic-функции, которые обеспечивают такую же гибкость.
Вы организуете код в двух файлах:
wrapper.go: Define ваш generic-контейнер и функции преобразования.Create generic-структуру
Wrapper[T any]с одним полемValueтипаT.Add метод
GetдляWrapper[T], который returns обёрнутое значение. Этот метод использует Parameter типа структуры, что allowed.Create отдельную generic-функцию
Transform[T, U any](w Wrapper[T], fn func(T) U) U, которая применяет функцию преобразования к значению обёртки и returns результат. Этой функции нужен собственный Parameter типаUдля выходного типа, поэтому она must быть отдельной функцией, а не методом.Create ещё одну отдельную функцию
TransformToString[T any](w Wrapper[T]) string, которая преобразует обёрнутое значение в строку с помощьюfmt.Sprintf("%v", ...).main.go: Продемонстрируйте шаблон обходного решения с различными преобразованиями.Прочитайте индикатор типа (
intилиstring), затем прочитайте значение. CreateWrapperсоответствующего типа.Для входного значения
int: CreateWrapper[int], затем используйтеTransform, чтобы удвоить значение (возвращая значение типа int), и используйтеTransformToString, чтобы получить строковое representation.Для входного значения
string: CreateWrapper[string], затем используйтеTransform, чтобы получить length строки (возвращая значение типа int), и используйтеTransformToString, чтобы получить строковое representation.Выведите results в следующем формате:
Original: [value] Transformed: [transformed value] As String: [string representation]
Будут предоставлены следующие входные данные:
- Строка 1: индикатор типа (
intилиstring) - Строка 2: значение
Например, для:
int
25Ваш вывод должен быть таким:
Original: 25
Transformed: 50
As String: 25А для:
string
Hello WorldВаш вывод должен быть таким:
Original: Hello World
Transformed: 11
As String: Hello WorldКлючевая идея заключается в том, что Transform принимает Wrapper[T] в качестве первого аргумента и introduces новый Parameter типа U для возвращаемого типа. Это было бы невозможно в качестве метода структуры. Этот шаблон предоставляет гибкость generic-преобразований при работе в рамках ограничений системы типов Go.
Попробуйте сами
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Прочитать индикатор типа
typeIndicator, _ := reader.ReadString('\n')
typeIndicator = strings.TrimSpace(typeIndicator)
// Прочитать значение
value, _ := reader.ReadString('\n')
value = strings.TrimSpace(value)
if typeIndicator == "int" {
// Разобрать целочисленное значение
num, _ := strconv.Atoi(value)
// TODO: Создать Wrapper[int] с разобранным числом
// TODO: Использовать Transform, чтобы удвоить значение (подсказка: передать функцию, которая удваивает)
// TODO: Использовать TransformToString, чтобы получить строковое представление
// TODO: Вывести результаты в требуемом формате:
// Original: [value]
// Transformed: [transformed value]
// As String: [string representation]
_ = num // Удалите эту строку, когда будете использовать num
} else if typeIndicator == "string" {
// TODO: Создать Wrapper[string] со значением
// TODO: Используйте Transform, чтобы получить длину строки (подсказка: передайте функцию, которая возвращает len())
// TODO: Использовать TransformToString, чтобы получить строковое представление
// TODO: Вывести результаты в требуемом формате:
// Original: [value]
// Transformed: [transformed value]
// As String: [string representation]
_ = value // Удалите эту строку, когда будете использовать value
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
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