Menu
Coddy logo textTech

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.

challenge icon

Desafio

Fácil

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

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

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

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

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

    Para uma entrada string: crie um Wrapper[string], depois 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

A 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
	}
}
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

Pratique por conta própria: Compilador de Go online