Soluzione alternativa per i metodi generici
Fa parte della sezione Programmazione orientata agli oggetti del percorso GO di Coddy. Lezione 71 di 107.
Go ha una limitazione degna di nota: non puoi definire metodi con parametri di tipo propri su una struct. Sebbene le struct generiche funzionino benissimo, non è consentito aggiungere un metodo che introduce un nuovo parametro di tipo.
Questo codice non verrà compilato:
type Box[T any] struct {
Value T
}
// ERROR: il metodo non deve avere parametri di tipo
func (b Box[T]) Convert[U any](fn func(T) U) U {
return fn(b.Value)
}La soluzione alternativa consiste nell'usare una funzione generica autonoma invece di un metodo:
type Box[T any] struct {
Value T
}
// Metodo normale - usa il parametro di tipo della struct
func (b Box[T]) Get() T {
return b.Value
}
// Funzione indipendente - può avere i propri parametri di tipo
func Convert[T, U any](b Box[T], fn func(T) U) U {
return fn(b.Value)
}
func main() {
intBox := Box[int]{Value: 42}
// Usa il metodo
fmt.Println(intBox.Get()) // 42
// Usa la funzione indipendente
strResult := Convert(intBox, func(n int) string {
return fmt.Sprintf("Number: %d", n)
})
fmt.Println(strResult) // Number: 42
}I metodi delle struct generiche possono comunque usare il parametro di tipo T della struct. La restrizione si applica solo all'introduzione di parametri di tipo aggiuntivi nella firma del metodo.
Quando ti serve quella flessibilità, una funzione generica che accetta la struct come primo argomento ottiene lo stesso risultato.
Sfida
FacileCostruiamo un toolkit di trasformazione dei dati che dimostra la soluzione alternativa al limite di Go sui metodi generici! Poiché i metodi non possono introdurre nuovi parametri di tipo, creerai funzioni generiche autonome che offrono la stessa flessibilità.
Organizzerai il codice in due file:
wrapper.go: Definisci il tuo contenitore generico e le funzioni di trasformazione.Crea una struct generica
Wrapper[T any]con un singolo campoValuedi tipoT.Aggiungi un metodo
GetaWrapper[T]che restituisce il valore contenuto. Questo metodo usa il parametro di tipo della struct, cosa consentita.Crea una funzione generica autonoma
Transform[T, U any](w Wrapper[T], fn func(T) U) Uche applica la funzione di trasformazione al valore contenuto nel wrapper e restituisce il risultato. Questa funzione ha bisogno di un proprio parametro di tipoUper il tipo di output, ed è per questo che deve essere una funzione autonoma anziché un metodo.Crea un'altra funzione autonoma
TransformToString[T any](w Wrapper[T]) stringche converte in stringa il valore contenuto usandofmt.Sprintf("%v", ...).main.go: Mostra il modello della soluzione alternativa con diverse trasformazioni.Leggi un indicatore di tipo (
intostring), poi leggi un valore. Crea unWrapperdel tipo appropriato.Per un input
int: crea unWrapper[int], poi usaTransformper raddoppiare il valore (restituendo un int) e usaTransformToStringper ottenere una rappresentazione in stringa.Per un input
string: crea unWrapper[string], poi usaTransformper ottenere la lunghezza della stringa (restituendo un int) e usaTransformToStringper ottenere la rappresentazione in stringa.Stampa i risultati in questo formato:
Original: [value] Transformed: [transformed value] As String: [string representation]
Verranno forniti i seguenti input:
- Riga 1: indicatore di tipo (
intostring) - Riga 2: il valore
Per esempio, dato:
int
25L'output dovrebbe essere:
Original: 25
Transformed: 50
As String: 25E dato:
string
Hello WorldL'output dovrebbe essere:
Original: Hello World
Transformed: 11
As String: Hello WorldL'idea chiave è che Transform accetta un Wrapper[T] come primo argomento e introduce un nuovo parametro di tipo U per il tipo restituito. Una cosa che non sarebbe possibile come metodo della struct. Questo modello ti offre la flessibilità delle trasformazioni generiche rispettando i vincoli del sistema di tipi di Go.
Provalo tu
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Leggi l'indicatore del tipo
typeIndicator, _ := reader.ReadString('\n')
typeIndicator = strings.TrimSpace(typeIndicator)
// Leggi il valore
value, _ := reader.ReadString('\n')
value = strings.TrimSpace(value)
if typeIndicator == "int" {
// Converti il valore in un intero
num, _ := strconv.Atoi(value)
// TODO: Crea un Wrapper[int] con il numero ottenuto
// TODO: Usa Transform per raddoppiare il valore (suggerimento: passa una funzione che lo raddoppi)
// TODO: Usa TransformToString per ottenere la rappresentazione come stringa
// TODO: Stampa i risultati nel formato richiesto:
// Original: [value]
// Transformed: [valore trasformato]
// As String: [rappresentazione come stringa]
_ = num // Rimuovi questa riga quando usi num
} else if typeIndicator == "string" {
// TODO: Crea un Wrapper[string] con il valore
// TODO: Usa Transform per ottenere la lunghezza della stringa (suggerimento: passa una funzione che restituisce len())
// TODO: Usa TransformToString per ottenere la rappresentazione come stringa
// TODO: Stampa i risultati nel formato richiesto:
// Original: [value]
// Transformed: [valore trasformato]
// As String: [rappresentazione come stringa]
_ = value // Rimuovi questa riga quando usi value
}
}
Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.
Tutte le lezioni di Programmazione orientata agli oggetti
1Fondamenti di OOP in Go
File esterniWorkspace e moduli GoPacchetti e importazioniNomi esportati e non esportatiIntroduzione all’OOP in GoGli struct come classiDefinire metodi sugli structReceiver con puntatore vs receiver con valoreInizializzazione degli structFunzioni costruttriciRiepilogo - Calcolatrice semplice4Interfacce
Introduzione alle interfacceImplementazione implicitaL’interfaccia come contrattoInterfaccia vuota (any)Asserzione di tipoSwitch sul tipoComposizione di interfacceInterfacce Stringer ed ErrorRipasso - Calcolatore di forme7Incapsulamento
Campi esportati e non esportatiIncapsulamento a livello di packageMetodi Getter e SetterNascondimento delle informazioni in GoRiepilogo - Registri degli studenti10Tipi generici (Go 1.18+)
Introduzione ai tipi genericiParametri di tipoVincoli di tipoStruct genericheSoluzione alternativa per i metodi genericiRiepilogo - Collezione generica13Modelli di progettazione Parte 1
Introduzione ai modelli di progettazioneModello SingletonModello FactoryModello Abstract FactoryModello ObserverModello Strategy2Approfondimento su tipi e struct
Tipi di base e compostiDefinizioni di tipi personalizzatiTag delle structStruct anonimeStruct annidateValori zero e predefinitiRipasso - Rubrica5Composizione invece dell'ereditarietà
Perché Go non ha l'ereditarietàFondamenti dell'embedding delle structPromozione dei metodiEmbedding di più structEmbedding e aggregazione a confrontoMascheramento dei metodi embeddedRiepilogo: gerarchia dei dipendenti8Gestione degli errori e OOP
L'interfaccia errorTipi di errore personalizzatiWrapping degli errori (fmt.Errorf)Errori sentinellaerrors.Is() ed errors.As()Panic, Defer e RecoverRiepilogo - Parser di file11Libreria standard e OOP
io.Reader e io.Writersort.InterfaceInterfaccia fmt.Stringerencoding/json con le structInterfaccia http.HandlerRipasso - modelli REST API14Modelli di progettazione – Parte 2
Pattern CommandPattern AdapterPattern DecoratorPattern Template MethodPattern StatePattern CompositeMiddleware come Decorator3Puntatori e memoria
Concetti base dei puntatori in GoPuntatori a structPassaggio per valore vs per riferimentoLa funzione new()Garbage collection in GoRiepilogo - Costruttore di liste concatenate6Polimorfismo in Go
Polimorfismo tramite interfacceDuck typing in GoRegole di soddisfacimento delle interfacceCollezioni polimorficheIniezione delle dipendenzeRiepilogo - Elaboratore di pagamenti9Concorrenza e OOP
Basi delle goroutineCanali e comunicazioneCanali bufferizzati vs non bufferizzatiIstruzione selectsync.Mutex e sync.RWMutexsync.WaitGroupProgettazione di struct thread-safeRiepilogo: worker poolEsercitati da solo: Compilatore Go online