Solução Alternativa para Métodos Genéricos
Parte da seção Programação Orientada a Objetos do Journey de GO da Coddy — lição 71 de 107.
Go tem uma limitação notável: você não pode definir métodos com seus próprios parâmetros de tipo em uma struct. Embora structs genéricas funcionem muito bem, adicionar um método que introduz um novo parâmetro de tipo não é permitido.
Este código não será compilado:
type Box[T any] struct {
Value T
}
// ERRO: o método não deve ter parâmetros de tipo
func (b Box[T]) Convert[U any](fn func(T) U) U {
return fn(b.Value)
}A solução alternativa é usar uma função genérica independente em vez de um método:
type Box[T any] struct {
Value T
}
// Método regular - usa o parâmetro de tipo da struct
func (b Box[T]) Get() T {
return b.Value
}
// Função independente - pode ter seus próprios parâmetros de 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 o método
fmt.Println(intBox.Get()) // 42
// Usa a função independente
strResult := Convert(intBox, func(n int) string {
return fmt.Sprintf("Number: %d", n)
})
fmt.Println(strResult) // Number: 42
}Métodos em structs genéricas ainda podem usar o parâmetro de tipo da struct T. A restrição se aplica apenas à introdução de parâmetros de tipo adicionais na assinatura do método.
Quando você precisa dessa flexibilidade, uma função genérica que recebe a struct como seu primeiro argumento alcança o mesmo resultado.
Desafio
FácilVamos construir um kit de ferramentas de transformação de dados que demonstra a solução alternativa para a limitação do Go em métodos genéricos! Como os métodos não podem introduzir novos parâmetros de tipo, você criará funções genéricas independentes que alcançam a mesma flexibilidade.
Você organizará seu código em dois arquivos:
wrapper.go: Defina seu contêiner genérico e as funções de transformação.Crie uma struct genérica
Wrapper[T any]com um único campoValuedo tipoT.Adicione um método
GetemWrapper[T]que retorna o valor encapsulado. Este método usa o parâmetro de tipo da struct, o que é permitido.Crie uma função genérica independente
Transform[T, U any](w Wrapper[T], fn func(T) U) Uque aplica a função de transformação ao valor do wrapper e retorna o resultado. Esta função precisa de seu próprio parâmetro de tipoUpara o tipo de saída, e é por isso que ela deve ser uma função independente em vez de um método.Crie outra função independente
TransformToString[T any](w Wrapper[T]) stringque converte o valor encapsulado em uma string usandofmt.Sprintf("%v", ...).main.go: Demonstre o padrão de solução alternativa com diferentes transformações.Leia um indicador de tipo (
intoustring), depois leia um valor. Crie umWrapperdo tipo apropriado.Para entrada
int: Crie umWrapper[int], useTransformpara dobrar o valor (retornando um int) e useTransformToStringpara obter uma representação em string.Para entrada
string: Crie umWrapper[string], useTransformpara obter o comprimento da string (retornando um int) e useTransformToStringpara obter a representação em string.Imprima os resultados neste formato:
Original: [value] Transformed: [transformed value] As String: [string representation]
As seguintes entradas serão fornecidas:
- Linha 1: Indicador de tipo (
intoustring) - Linha 2: O valor
Por exemplo, dado:
int
25Sua saída deve ser:
Original: 25
Transformed: 50
As String: 25E dado:
string
Hello WorldSua saída deve ser:
Original: Hello World
Transformed: 11
As String: Hello WorldO ponto principal é que Transform recebe um Wrapper[T] como seu primeiro argumento e introduz um novo parâmetro de tipo U para o tipo de retorno — algo que não seria possível como um método na struct. Esse padrão oferece a flexibilidade de transformações genéricas enquanto trabalha dentro das restrições do sistema de tipos do Go.
Folha de consulta
Go não permite que métodos em structs genéricas introduzam seus próprios parâmetros de tipo. Os métodos só podem usar os parâmetros de tipo existentes da struct.
Este código não compilará:
type Box[T any] struct {
Value T
}
// ERROR: method must have no type parameters
func (b Box[T]) Convert[U any](fn func(T) U) U {
return fn(b.Value)
}A solução alternativa é usar funções genéricas independentes em vez de métodos:
type Box[T any] struct {
Value T
}
// Método regular - usa o parâmetro de tipo da struct
func (b Box[T]) Get() T {
return b.Value
}
// Função independente - pode ter seus próprios parâmetros de 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 o método
fmt.Println(intBox.Get()) // 42
// Usa a função independente
strResult := Convert(intBox, func(n int) string {
return fmt.Sprintf("Number: %d", n)
})
fmt.Println(strResult) // Number: 42
}Métodos em structs genéricas podem usar o parâmetro de tipo da struct. A restrição se aplica apenas à introdução de parâmetros de tipo adicionais na assinatura do método. Quando você precisar dessa flexibilidade, use uma função genérica que receba a struct como seu primeiro argumento.
Experimente você mesmo
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Ler o indicador de tipo
typeIndicator, _ := reader.ReadString('\n')
typeIndicator = strings.TrimSpace(typeIndicator)
// Ler o valor
value, _ := reader.ReadString('\n')
value = strings.TrimSpace(value)
if typeIndicator == "int" {
// Analisar o valor inteiro
num, _ := strconv.Atoi(value)
// TODO: Criar um Wrapper[int] com o número analisado
// TODO: Usar Transform para dobrar o valor (dica: passe uma função que dobra)
// TODO: Usar TransformToString para obter a representação em string
// TODO: Imprimir os resultados no formato exigido:
// Original: [value]
// Transformed: [transformed value]
// As String: [string representation]
_ = num // Remova esta linha quando você usar num
} else if typeIndicator == "string" {
// TODO: Criar um Wrapper[string] com o valor
// TODO: Usar Transform para obter o comprimento da string (dica: passe uma função que retorna len())
// TODO: Usar TransformToString para obter a representação em string
// TODO: Imprimir os resultados no formato exigido:
// Original: [value]
// Transformed: [transformed value]
// As String: [string representation]
_ = value // Remova esta linha quando você usar value
}
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Programação Orientada a Objetos
1Fundamentos de OOP em Go
Arquivos ExternosGo Workspace e MódulosPacotes e ImportaçõesNomes Exportados vs Não ExportadosIntrodução a OOP em GoStructs como ClassesDefinindo Métodos em StructsRecebedores de Ponteiro vs ValorInicialização de StructsFunções ConstrutorasRecapitulação - Calculadora Simples4Interfaces
Introdução a InterfacesImplementação ImplícitaInterface como ContratoInterface Vazia (any)Type AssertionType SwitchComposição de InterfacesInterfaces Stringer & ErrorRecapitulação - Calculadora de Formas7Encapsulamento
Campos Exportados vs Não ExportadosEncapsulamento em Nível de PacoteMétodos Getter e SetterOcultação de Informação em GoRecapitulação - Registros de Estudantes10Generics (Go 1.18+)
Introdução a GenericsParâmetros de TipoRestrições de TipoStructs GenéricasSolução Alternativa para Métodos GenéricosRecapitulação - Coleção Genérica2Mergulho Profundo em Tipos e Structs
Tipos Básicos e CompostosDefinições de Tipos PersonalizadosStruct TagsStructs AnônimasStructs AninhadasZero Values e PadrõesRecapitulação - Agenda de Contatos5Composição em vez de Herança
Por que Go não possui HerançaFundamentos de Incorporação de StructsPromoção de MétodosIncorporando Múltiplas StructsIncorporação vs AgregaçãoShadowing de Métodos IncorporadosRecapitulação - Hierarquia de Funcionários8Tratamento de Erros e OOP
A Interface errorTipos de Erro PersonalizadosError Wrapping (fmt.Errorf)Erros Sentinelaerrors.Is() e errors.As()Panic, Defer e RecoverRecapitulação - File Parser3Ponteiros e Memória
Fundamentos de Ponteiros em GoPonteiros para StructsPassagem por Valor vs ReferênciaA Função new()Garbage Collection em GoRecapitulação - Construtor de Lista Encadeada6Polimorfismo em Go
Polimorfismo via InterfacesDuck Typing em GoRegras de Satisfação de InterfaceColeções PolimórficasInjeção de DependênciaRecapitulação - Processador de Pagamentos9Concorrência e OOP
Fundamentos de GoroutinesCanais e ComunicaçãoCanais com Buffer vs. Sem BufferInstrução Selectsync.Mutex e sync.RWMutexsync.WaitGroupDesign de Structs Thread-SafeRecapitulação - Worker Pool