Menu
Coddy logo textTech

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.

challenge icon

Desafio

Fácil

Vamos 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 campo Value do tipo T.

    Adicione um método Get em Wrapper[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) U que 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 tipo U para 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]) string que converte o valor encapsulado em uma string usando fmt.Sprintf("%v", ...).

  • main.go: Demonstre o padrão de solução alternativa com diferentes transformações.

    Leia um indicador de tipo (int ou string), depois leia um valor. Crie um Wrapper do tipo apropriado.

    Para entrada int: Crie um Wrapper[int], use Transform para dobrar o valor (retornando um int) e use TransformToString para obter uma representação em string.

    Para entrada string: Crie um Wrapper[string], use Transform para obter o comprimento da string (retornando um int) e use TransformToString para 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 (int ou string)
  • Linha 2: O valor

Por exemplo, dado:

int
25

Sua saída deve ser:

Original: 25
Transformed: 50
As String: 25

E dado:

string
Hello World

Sua saída deve ser:

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

O 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
	}
}
quiz iconTeste seus conhecimentos

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