Menu
Coddy logo textTech

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

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

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

challenge icon

Задание

Легко

Давайте создадим набор инструментов для преобразования данных, демонстрирующий обходное решение ограничения 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), затем прочитайте значение. Create Wrapper соответствующего типа.

    Для входного значения int: Create Wrapper[int], затем используйте Transform, чтобы удвоить значение (возвращая значение типа int), и используйте TransformToString, чтобы получить строковое representation.

    Для входного значения string: Create Wrapper[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
	}
}
quiz iconПроверьте себя

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

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

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