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.
Wyzwanie
ŁatwyZbudujmy 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 polemValuetypuT.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 typuUdla 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 (
intlubstring), a następnie wczytaj wartość. UtwórzWrapperodpowiedniego typu.Dla wejścia
int: UtwórzWrapper[int], a następnie użyjTransform, aby podwoić wartość (zwracającą wartość typu int), i użyjTransformToString, aby uzyskać tekstową reprezentację.Dla wejścia
string: UtwórzWrapper[string], a następnie użyjTransform, aby uzyskać długość ciągu znaków (zwracającą wartość typu int), i użyjTransformToString, 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 (
intlubstring) - Wiersz 2: Wartość
Na przykład dla danych:
int
25Wynik powinien być następujący:
Original: 25
Transformed: 50
As String: 25A dla danych:
string
Hello WorldWynik powinien być następujący:
Original: Hello World
Transformed: 11
As String: Hello WorldKluczowy 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
}
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy OOP w Go
Pliki zewnętrzneObszar roboczy i moduły GoPakiety i importyNazwy eksportowane i nieeksportowaneWprowadzenie do OOP w GoStruktury jako klasyDefiniowanie metod dla strukturOdbiorniki wskaźnikowe i wartościoweInicjalizacja strukturFunkcje konstruktoraPodsumowanie – prosty kalkulator4Interfejsy
Wprowadzenie do interfejsówImplementacja niejawnaInterfejs jako kontraktPusty interfejs (any)Asercja typuPrzełącznik typówKompozycja interfejsówInterfejsy Stringer i ErrorPowtórka — kalkulator figur7Hermetyzacja
Pola eksportowane i nieeksportowaneHermetyzacja na poziomie pakietuMetody pobierające i ustawiająceUkrywanie informacji w GoPowtórka – dane studentów10Typy generyczne (Go 1.18+)
Wprowadzenie do typów generycznychParametry typówOgraniczenia typówStruktury generyczneObejście ograniczeń metod generycznychPodsumowanie — generyczna kolekcja2Typy i struktury w praktyce
Typy proste i złożoneDefiniowanie własnych typówTagi pól strukturStruktury anonimoweStruktury zagnieżdżoneWartości zerowe i domyślnePodsumowanie – książka kontaktów5Kompozycja zamiast dziedziczenia
Dlaczego Go nie ma dziedziczeniaPodstawy osadzania strukturPromowanie metodOsadzanie wielu strukturOsadzanie a agregacjaPrzesłanianie osadzonych metodPodsumowanie — hierarchia pracowników8Obsługa błędów i programowanie obiektowe
Interfejs błęduWłasne typy błędówOpakowywanie błędów (fmt.Errorf)Błędy wartowniczeerrors.Is() i errors.As()Panic, Defer i RecoverPodsumowanie — parser plików3Wskaźniki i pamięć
Podstawy wskaźników w GoWskaźniki do strukturPrzekazywanie przez wartość a przez referencjęFunkcja new()Odśmiecanie pamięci w GoPowtórka — kreator listy wiązanej6Polimorfizm w Go
Polimorfizm za pomocą interfejsówDuck typing w GoZasady spełniania interfejsówKolekcje polimorficzneWstrzykiwanie zależnościPowtórka — procesor płatności9Współbieżność i OOP
Podstawy goroutinesKanały i komunikacjaKanały buforowane i niebuforowaneInstrukcja selectsync.Mutex i sync.RWMutexsync.WaitGroupProjektowanie struktur bezpiecznych wątkowoPowtórka – pula workerów12Zaawansowane zagadnienia programowania obiektowego
Wzorzec opcji funkcyjnychWzorzec Builder w GoŁańcuchowe wywoływanie metodAliasy typów a definicje typówPodstawy refleksjiPrzegląd generowania koduPoćwicz samodzielnie: Kompilator Go online