Menu
Coddy logo textTech

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.

challenge icon

Sfida

Facile

Costruiamo 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 campo Value di tipo T.

    Aggiungi un metodo Get a Wrapper[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) U che applica la funzione di trasformazione al valore contenuto nel wrapper e restituisce il risultato. Questa funzione ha bisogno di un proprio parametro di tipo U per 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]) string che converte in stringa il valore contenuto usando fmt.Sprintf("%v", ...).

  • main.go: Mostra il modello della soluzione alternativa con diverse trasformazioni.

    Leggi un indicatore di tipo (int o string), poi leggi un valore. Crea un Wrapper del tipo appropriato.

    Per un input int: crea un Wrapper[int], poi usa Transform per raddoppiare il valore (restituendo un int) e usa TransformToString per ottenere una rappresentazione in stringa.

    Per un input string: crea un Wrapper[string], poi usa Transform per ottenere la lunghezza della stringa (restituendo un int) e usa TransformToString per 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 (int o string)
  • Riga 2: il valore

Per esempio, dato:

int
25

L'output dovrebbe essere:

Original: 25
Transformed: 50
As String: 25

E dato:

string
Hello World

L'output dovrebbe essere:

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

L'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
	}
}
quiz iconMettiti alla prova

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

Esercitati da solo: Compilatore Go online