Menu
Coddy logo textTech

Obejście ograniczeń metod generycznych

Część sekcji Programowanie obiektowe ścieżki GO w Coddy. Lekcja 71 z 107.

Go ma istotne ograniczenie: nie możesz definiować metod z własnymi parametrami typu w strukturze. Chociaż generyczne struktury sprawdzają się świetnie, dodanie metody wprowadzającej nowy parametr typu jest niedozwolone.

Ten kod się nie skompiluje:

type Box[T any] struct {
    Value T
}

// ERROR: metoda nie może mieć parametrów typu
func (b Box[T]) Convert[U any](fn func(T) U) U {
    return fn(b.Value)
}

Rozwiązaniem jest użycie samodzielnej funkcji generycznej zamiast metody:

type Box[T any] struct {
    Value T
}

// Zwykła metoda — używa parametru typu struktury
func (b Box[T]) Get() T {
    return b.Value
}

// Samodzielna funkcja — może mieć własne parametry typu
func Convert[T, U any](b Box[T], fn func(T) U) U {
    return fn(b.Value)
}

func main() {
    intBox := Box[int]{Value: 42}
    
    // Użyj metody
    fmt.Println(intBox.Get())  // 42
    
    // Użyj samodzielnej funkcji
    strResult := Convert(intBox, func(n int) string {
        return fmt.Sprintf("Number: %d", n)
    })
    fmt.Println(strResult)  // Number: 42
}

Metody struktur generycznych nadal mogą używać parametru typu struktury T. Ograniczenie dotyczy tylko wprowadzania dodatkowych parametrów typu w sygnaturze metody.

Gdy potrzebujesz takiej elastyczności, funkcja generyczna, która przyjmuje strukturę jako pierwszy argument, zapewnia ten sam efekt.

challenge icon

Wyzwanie

Łatwy

Zbudujmy zestaw narzędzi do przekształcania danych, który pokazuje, jak obejść ograniczenie Go dotyczące metod generycznych! Ponieważ metody nie mogą wprowadzać nowych parametrów typów, utworzysz samodzielne funkcje generyczne, które zapewniają tę samą elastyczność.

Rozmieścisz kod w dwóch plikach:

  • wrapper.go: Zdefiniuj swój generyczny kontener i funkcje przekształcające.

    Utwórz generyczną strukturę Wrapper[T any] z jednym polem Value typu T.

    Dodaj do Wrapper[T] metodę Get, która zwraca opakowaną wartość. Ta metoda korzysta z parametru typu struktury, co jest dozwolone.

    Utwórz samodzielną funkcję generyczną Transform[T, U any](w Wrapper[T], fn func(T) U) U, która stosuje funkcję przekształcającą do wartości opakowanej w kontenerze i zwraca wynik. Ta funkcja potrzebuje własnego parametru typu U dla typu wyjściowego, dlatego musi być samodzielną funkcją, a nie metodą.

    Utwórz kolejną samodzielną funkcję TransformToString[T any](w Wrapper[T]) string, która konwertuje opakowaną wartość na ciąg znaków za pomocą fmt.Sprintf("%v", ...).

  • main.go: Pokaż wzorzec obejścia z różnymi przekształceniami.

    Wczytaj wskaźnik typu (int lub string), a następnie wczytaj wartość. Utwórz Wrapper odpowiedniego typu.

    Dla wejścia int: Utwórz Wrapper[int], a następnie użyj Transform, aby podwoić wartość (zwracającą wartość typu int), i użyj TransformToString, aby uzyskać tekstową reprezentację.

    Dla wejścia string: Utwórz Wrapper[string], a następnie użyj Transform, aby uzyskać długość ciągu znaków (zwracającą wartość typu int), i użyj TransformToString, aby uzyskać tekstową reprezentację.

    Wypisz wyniki w tym formacie:

    Original: [value]
    Transformed: [transformed value]
    As String: [string representation]

Dane wejściowe będą następujące:

  • Wiersz 1: Wskaźnik typu (int lub string)
  • Wiersz 2: Wartość

Na przykład dla danych:

int
25

Wynik powinien być następujący:

Original: 25
Transformed: 50
As String: 25

A dla danych:

string
Hello World

Wynik powinien być następujący:

Original: Hello World
Transformed: 11
As String: Hello World

Kluczowy wniosek jest taki, że Transform przyjmuje Wrapper[T] jako pierwszy argument i wprowadza nowy parametr typu U dla typu zwracanego. Nie byłoby to możliwe w przypadku metody struktury. Ten wzorzec zapewnia elastyczność generycznych przekształceń, pozwalając jednocześnie działać w ramach ograniczeń systemu typów Go.

Spróbuj swoich sił

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)
	
	// Odczytaj wskaźnik typu
	typeIndicator, _ := reader.ReadString('\n')
	typeIndicator = strings.TrimSpace(typeIndicator)
	
	// Odczytaj wartość
	value, _ := reader.ReadString('\n')
	value = strings.TrimSpace(value)
	
	if typeIndicator == "int" {
		// Przekształć wartość na liczbę całkowitą
		num, _ := strconv.Atoi(value)
		
		// TODO: Utwórz Wrapper[int] ze sparsowaną liczbą
		
		// TODO: Użyj Transform, aby podwoić wartość (wskazówka: przekaż funkcję, która ją podwaja)
		
		// TODO: Użyj TransformToString, aby uzyskać reprezentację tekstową
		
		// TODO: Wypisz wyniki w wymaganym formacie:
		// Original: [value]
		// Transformed: [przekształcona wartość]
		// As String: [reprezentacja tekstowa]
		
		_ = num // Usuń ten wiersz, gdy użyjesz num
	} else if typeIndicator == "string" {
		// TODO: Utwórz Wrapper[string] z wartością
		
		// TODO: Użyj Transform, aby uzyskać długość ciągu znaków (wskazówka: przekaż funkcję, która zwraca len())
		
		// TODO: Użyj TransformToString, aby uzyskać reprezentację tekstową
		
		// TODO: Wypisz wyniki w wymaganym formacie:
		// Original: [value]
		// Transformed: [przekształcona wartość]
		// As String: [reprezentacja tekstowa]
		
		_ = value // Usuń tę linię, gdy użyjesz value
	}
}
quiz iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator Go online