Menu
Coddy logo textTech

Обходной путь для обобщенных методов

Часть раздела Объектно-ориентированное программирование путешествия по 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. Ограничение распространяется только на введение дополнительных параметров типа в сигнатуре метода.

Когда вам нужна такая гибкость, обобщенная функция, принимающая структуру в качестве первого аргумента, позволяет достичь того же результата.

challenge icon

Задание

Легко

Давайте создадим набор инструментов для трансформации данных, который демонстрирует обходной путь для ограничения 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
	}
}
quiz iconПроверьте себя

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

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