Обходной путь для обобщенных методов
Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy — урок 71 из 107.
У Go есть заметное ограничение: вы не можете определять методы с их собственными параметрами типа для структуры. Хотя универсальные структуры работают отлично, добавление метода, который вводит новый параметр типа, не допускается.
Этот код не скомпилируется:
type Box[T any] struct {
Value T
}
// ОШИБКА: метод не должен иметь параметров типа
func (b Box[T]) Convert[U any](fn func(T) U) U {
return fn(b.Value)
}Обходной путь заключается в использовании отдельной универсальной функции вместо метода:
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
}Методы обобщенных структур все еще могут использовать параметр типа структуры T. Ограничение распространяется только на введение дополнительных параметров типа в сигнатуре метода.
Когда вам нужна такая гибкость, обобщенная функция, принимающая структуру в качестве первого аргумента, позволяет достичь того же результата.
Задание
ЛегкоДавайте создадим набор инструментов для трансформации данных, который демонстрирует обходной путь для ограничения Go в отношении универсальных (generic) методов! Поскольку методы не могут вводить новые параметры типа, вы создадите отдельные универсальные функции, которые обеспечивают ту же гибкость.
Вы организуете свой код в двух файлах:
wrapper.go: Определите ваш универсальный контейнер и функции трансформации.Создайте универсальную структуру
Wrapper[T any]с единственным полемValueтипаT.Добавьте метод
GetдляWrapper[T], который возвращает обернутое значение. Этот метод использует параметр типа структуры, что разрешено.Создайте отдельную универсальную функцию
Transform[T, U any](w Wrapper[T], fn func(T) U) U, которая применяет функцию трансформации к значению обертки и возвращает результат. Этой функции нужен собственный параметр типаUдля выходного типа, поэтому она должна быть отдельной функцией, а не методом.Создайте еще одну отдельную функцию
TransformToString[T any](w Wrapper[T]) string, которая преобразует обернутое значение в строку с помощьюfmt.Sprintf("%v", ...).main.go: Продемонстрируйте паттерн обходного пути с различными трансформациями.Прочитайте индикатор типа (
intилиstring), затем прочитайте значение. СоздайтеWrapperсоответствующего типа.Для ввода
int: СоздайтеWrapper[int], затем используйтеTransform, чтобы удвоить значение (возвращая int), и используйтеTransformToString, чтобы получить строковое представление.Для ввода
string: СоздайтеWrapper[string], затем используйтеTransform, чтобы получить длину строки (возвращая int), и используйтеTransformToString, чтобы получить строковое представление.Выведите результаты в следующем формате:
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] в качестве первого аргумента и вводит новый параметр типа U для возвращаемого типа — то, что было бы невозможно реализовать как метод структуры. Этот паттерн дает вам гибкость универсальных трансформаций, работая в рамках ограничений системы типов Go.
Шпаргалка
Go не позволяет методам универсальных структур вводить собственные параметры типов. Методы могут использовать только существующие параметры типов структуры.
Этот код не скомпилируется:
type Box[T any] struct {
Value T
}
// ERROR: method must have no type parameters
func (b Box[T]) Convert[U any](fn func(T) U) U {
return fn(b.Value)
}Обходной путь заключается в использовании отдельных универсальных функций вместо методов:
type Box[T any] struct {
Value T
}
// Regular method - uses the struct's type parameter
func (b Box[T]) Get() T {
return b.Value
}
// Standalone function - can have its own type parameters
func Convert[T, U any](b Box[T], fn func(T) U) U {
return fn(b.Value)
}
func main() {
intBox := Box[int]{Value: 42}
// Use the method
fmt.Println(intBox.Get()) // 42
// Use the standalone function
strResult := Convert(intBox, func(n int) string {
return fmt.Sprintf("Number: %d", n)
})
fmt.Println(strResult) // Number: 42
}Методы универсальных структур могут использовать параметр типа структуры. Ограничение касается только введения дополнительных параметров типов в сигнатуре метода. Если вам нужна такая гибкость, используйте универсальную функцию, которая принимает структуру в качестве первого аргумента.
Попробуйте сами
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