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, não é permitido adicionar um método que introduza um parâmetro de tipo novo.
Este código não será compilado:
type Box[T any] struct {
Value T
}
// ERROR: 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}
// Use o método
fmt.Println(intBox.Get()) // 42
// Use a função independente
strResult := Convert(intBox, func(n int) string {
return fmt.Sprintf("Number: %d", n)
})
fmt.Println(strResult) // Número: 42
}Os 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 criar um kit de ferramentas de transformação de dados que demonstra a solução alternativa para a limitação do Go com 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 oferecem 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
GetaWrapper[T]que retorne o valor encapsulado. Esse 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 aplique a função de transformação ao valor do wrapper e retorne o resultado. Essa função precisa de seu próprio parâmetro de tipoUpara o tipo de saída, e é por isso que ela precisa ser uma função independente em vez de um método.Crie outra função independente
TransformToString[T any](w Wrapper[T]) stringque converta o valor encapsulado em uma string usandofmt.Sprintf("%v", ...).main.go: Demonstre o padrão da solução alternativa com diferentes transformações.Leia um indicador de tipo (
intoustring) e, em seguida, leia um valor. Crie umWrapperdo tipo apropriado.Para uma entrada
int: crie umWrapper[int], depois useTransformpara dobrar o valor (retornando um int) e useTransformToStringpara obter uma representação em string.Para uma entrada
string: crie umWrapper[string], depois 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 WorldA ideia principal é que Transform recebe um Wrapper[T] como primeiro argumento e introduz um novo parâmetro de tipo U para o tipo de retorno. Isso não seria possível como um método da struct. Esse padrão oferece a flexibilidade das transformações genéricas enquanto trabalha dentro das restrições do sistema de tipos do Go.
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 usar num
} else if typeIndicator == "string" {
// TODO: Criar um Wrapper[string] com o valor
// TODO: Use 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 ExternosWorkspace e Módulos do GoPacotes e ImportsNomes Exportados vs. Não ExportadosIntrodução à OOP em GoStructs como ClassesDefinindo Métodos em StructsReceptores de Ponteiro vs. de ValorInicialização de StructsFunções ConstrutorasRecapitulação — Calculadora Simples4Interfaces
Introdução às InterfacesImplementação ImplícitaInterface como ContratoInterface Vazia (any)Asserção de TipoSwitch de TipoComposição de InterfacesInterfaces Stringer e ErrorRecapitulação - Calculadora de Formas7Encapsulamento
Campos Exportados vs. Não ExportadosEncapsulamento no Nível de PacoteMétodos Getters e SettersOcultação de Informações em GoRecapitulação - Registros de Alunos10Genéricos (Go 1.18+)
Introdução aos genéricosParâmetros de tipoRestrições de tipoStructs genéricasAlternativa para métodos genéricosRecapitulação - Coleção genérica13Padrões de Projeto — Parte 1
Introdução aos Padrões de ProjetoPadrão SingletonPadrão FactoryPadrão Abstract FactoryPadrão ObserverPadrão Strategy2Tipos e Structs em Profundidade
Tipos Básicos e CompostosDefinições de Tipos PersonalizadosTags de StructStructs AnônimasStructs AninhadasValores Zero e PadrõesRecapitulação - Livro de Contatos5Composição em vez de herança
Por que Go não tem herançaNoções básicas de incorporação de structsPromoção de métodosIncorporação de várias structsIncorporação vs agregaçãoOcultação de métodos incorporadosRecapitulação - Hierarquia de funcionários8Tratamento de Erros e OOP
A interface errorTipos de Erro PersonalizadosEncapsulamento de Erros (fmt.Errorf)Erros Sentinelaerrors.Is() e errors.As()Panic, Defer e RecoverRecapitulação - Analisador de Arquivos3Ponteiros e Memória
Fundamentos de Ponteiros em GoPonteiros para StructsPassagem por Valor vs. ReferênciaA Função new()Coleta de Lixo em GoRecapitulação - Construtor de Lista Ligada6Polimorfismo em Go
Polimorfismo via InterfacesDuck Typing em GoRegras de Satisfação de InterfacesColeções PolimórficasInjeção de DependênciasRecapitulação - Processador de Pagamentos9Concorrência e OOP
Fundamentos de GoroutinesCanais e comunicaçãoCanais com e sem bufferInstrução selectsync.Mutex e sync.RWMutexsync.WaitGroupDesign de structs thread-safeRevisão - Worker PoolPratique por conta própria: Compilador de Go online