Nivel 6: Creemos un Convertidor de Unidades en Go

 

Convertir unidades con golang

Nivel 6: Creemos un Convertidor de Unidades en Go

👋 En este post nos sumergiremos en la creación de un Convertidor de Unidades en Go. Este proyecto no solo es práctico, sino que también nos permitirá explorar conceptos fundamentales como interfaces, mapas, y el manejo de errores. ¡Vamos a continuar aprendiendo haciendo!

🎯 Objetivo del Proyecto

Nuestro objetivo es crear un convertidor de unidades que pueda manejar diferentes tipos de conversiones: longitud, peso, y temperatura. Este proyecto nos ayudará a entender mejor cómo Go utiliza interfaces para crear código flexible y cómo los mapas pueden ser herramientas poderosas para almacenar datos relacionados.

🧱 Estructura del Proyecto

Comenzaremos definiendo la estructura básica de nuestro proyecto:

package main import ( "fmt" "math" "errors" ) // Interfaces y estructuras irán aquí func main() { // Código principal irá aquí }

1. Definiendo la Interfaz Converter

Primero, definamos una interfaz que todos nuestros convertidores implementarán:

type Converter interface { Convert(value float64, from, to string) (float64, error) }

Esta interfaz define un método Convert que toma un valor, una unidad de origen y una unidad de destino, y devuelve el valor convertido y un posible error.

2. Implementando el Convertidor de Longitud

Comencemos con el convertidor de longitud:

type LengthConverter struct { factors map[string]float64 } func NewLengthConverter() *LengthConverter { return &LengthConverter{ factors: map[string]float64{ "m": 1, "cm": 100, "mm": 1000, "km": 0.001, "ft": 3.28084, "yd": 1.09361, "mi": 0.000621371, }, } } func (lc *LengthConverter) Convert(value float64, from, to string) (float64, error) { fromFactor, fromOk := lc.factors[from] toFactor, toOk := lc.factors[to] if !fromOk || !toOk { return 0, errors.New("unidad de longitud no soportada") } meters := value / fromFactor return meters * toFactor, nil }

Explicación detallada:

  1. Struct LengthConverter:
    • Utilizamos un map para almacenar los factores de conversión. Los mapas en Go son estructuras de datos clave-valor, perfectas para este caso.
  2. Función NewLengthConverter:
    • Esta es una función "constructora" que inicializa nuestro convertidor con un mapa de factores de conversión.
    • Cada factor representa cuántas unidades de esa medida hay en un metro.
  3. Método Convert:
    • Primero, buscamos los factores de conversión en el mapa usando la sintaxis lc.factors[from].
    • El , ok en la asignación es un patrón común en Go para verificar si una clave existe en el mapa.
    • Si alguna unidad no está soportada, devolvemos un error usando errors.New().
    • La conversión se hace primero a metros y luego a la unidad destino.

3. Implementando el Convertidor de Peso

Siguiendo un patrón similar, implementemos el convertidor de peso:

type WeightConverter struct { factors map[string]float64 } func NewWeightConverter() *WeightConverter { return &WeightConverter{ factors: map[string]float64{ "kg": 1, "g": 1000, "lb": 2.20462, "oz": 35.274, "ton": 0.001, }, } } func (wc *WeightConverter) Convert(value float64, from, to string) (float64, error) { fromFactor, fromOk := wc.factors[from] toFactor, toOk := wc.factors[to] if !fromOk || !toOk { return 0, errors.New("unidad de peso no soportada") } kg := value / fromFactor return kg * toFactor, nil }

La estructura y lógica son similares al convertidor de longitud, pero con unidades de peso.

4. Implementando el Convertidor de Temperatura

El convertidor de temperatura es un poco diferente, ya que las conversiones no son lineales:

type TemperatureConverter struct{} func (tc *TemperatureConverter) Convert(value float64, from, to string) (float64, error) { var celsius float64 switch from { case "C": celsius = value case "F": celsius = (value - 32) * 5 / 9 case "K": celsius = value - 273.15 default: return 0, errors.New("unidad de temperatura de origen no soportada") } switch to { case "C": return celsius, nil case "F": return celsius*9/5 + 32, nil case "K": return celsius + 273.15, nil default: return 0, errors.New("unidad de temperatura de destino no soportada") } }

Aquí, usamos switch statements para manejar las diferentes conversiones. Primero convertimos a Celsius, y luego a la unidad deseada.

5. Creando el Convertidor Principal

Ahora, creemos un convertidor que pueda manejar todos los tipos:

type UnitConverter struct { lengthConverter Converter weightConverter Converter temperatureConverter Converter } func NewUnitConverter() *UnitConverter { return &UnitConverter{ lengthConverter: NewLengthConverter(), weightConverter: NewWeightConverter(), temperatureConverter: &TemperatureConverter{}, } } func (uc *UnitConverter) Convert(value float64, from, to, unitType string) (float64, error) { switch unitType { case "length": return uc.lengthConverter.Convert(value, from, to) case "weight": return uc.weightConverter.Convert(value, from, to) case "temperature": return uc.temperatureConverter.Convert(value, from, to) default: return 0, errors.New("tipo de unidad no soportado") } }

Este convertidor principal utiliza la interfaz Converter para manejar diferentes tipos de conversiones de manera uniforme.

6. Implementando la Interfaz de Usuario

La interfaz de usuario es crucial para que nuestro convertidor sea fácil de usar. Vamos a mejorarla y explicar cada parte en detalle:

func main() { converter := NewUnitConverter() for { fmt.Println("\n===== Convertidor de Unidades =====") fmt.Println("1. Longitud") fmt.Println("2. Peso") fmt.Println("3. Temperatura") fmt.Println("4. Salir") fmt.Print("Seleccione el tipo de conversión (1-4): ") choice := getUserInput() if choice == "4" { fmt.Println("¡Gracias por usar el Convertidor de Unidades! Hasta luego.") break } if choice < "1" || choice > "3" { fmt.Println("Opción no válida. Por favor, intente de nuevo.") continue } unitType := map[string]string{ "1": "length", "2": "weight", "3": "temperature", }[choice] fmt.Print("Ingrese el valor a convertir: ") value, err := getFloatInput() if err != nil { fmt.Println("Error:", err) continue } fmt.Print("Ingrese la unidad de origen: ") from := getUserInput() fmt.Print("Ingrese la unidad de destino: ") to := getUserInput() result, err := converter.Convert(value, from, to, unitType) if err != nil { fmt.Printf("Error: %v\n", err) } else { fmt.Printf("Resultado: %.2f %s = %.2f %s\n", value, from, result, to) } } } func getUserInput() string { var input string fmt.Scanln(&input) return strings.TrimSpace(input) } func getFloatInput() (float64, error) { input := getUserInput() value, err := strconv.ParseFloat(input, 64) if err != nil { return 0, errors.New("por favor, ingrese un número válido") } return value, nil }

Explicación detallada:

  1. Bucle principal:
    • Usamos un bucle for infinito para mantener el programa en ejecución hasta que el usuario decida salir.
    • Esto permite realizar múltiples conversiones sin tener que reiniciar el programa.
  2. Menú de opciones:
    • Presentamos un menú claro con las opciones numeradas.
    • Usamos fmt.Println() para mostrar cada opción en una nueva línea, mejorando la legibilidad.
  3. Obtención de entrada del usuario:
    • Creamos una función auxiliar getUserInput() para obtener y limpiar la entrada del usuario.
    • strings.TrimSpace() se usa para eliminar espacios en blanco al inicio y al final de la entrada.
  4. Manejo de la selección del usuario:
    • Verificamos si el usuario quiere salir (opción 4) antes de proceder.
    • Validamos que la entrada esté en el rango correcto (1-3) antes de continuar.
  5. Mapeo de selección a tipo de unidad:
    • Usamos un mapa para convertir la selección del usuario al tipo de unidad correspondiente.
    • Esto hace el código más limpio y fácil de mantener que un switch statement.
  6. Obtención del valor a convertir:
    • Creamos una función getFloatInput() para manejar la conversión de la entrada a un float64.
    • Usamos strconv.ParseFloat() para la conversión, manejando posibles errores.
  7. Realización de la conversión:
    • Llamamos al método Convert de nuestro UnitConverter.
    • Manejamos posibles errores y mostramos el resultado formateado.
  8. Formateo de la salida:
    • Usamos fmt.Printf() con formateadores (%.2f) para mostrar el resultado con 2 decimales.
  9. Manejo de errores:
    • Mostramos mensajes de error claros cuando algo va mal, mejorando la experiencia del usuario.
  10. Limpieza de la entrada:
    • strings.TrimSpace() asegura que espacios adicionales no causen problemas en la entrada.

🧪 Probando Nuestro Convertidor

Ahora puedes compilar y ejecutar tu programa:

go run main.go

Prueba diferentes conversiones, por ejemplo:

  • 1 km a m
  • 2.5 kg a lb
  • 32 F a C

Source Code

🎓 Conceptos Aprendidos

  1. Interfaces: Usamos la interfaz Converter para definir un comportamiento común.
  2. Structs y Métodos: Implementamos convertidores específicos como structs con métodos.
  3. Mapas: Utilizamos mapas para almacenar factores de conversión.
  4. Manejo de Errores: Devolvimos errores en caso de unidades no soportadas.
  5. Switch Statements: Los usamos para manejar diferentes tipos de conversiones.
  6. Paquete math: Aunque no lo usamos extensivamente en este ejemplo, es crucial para cálculos más complejos.

🚀 Retos Adicionales

  • Mostrar unidades disponibles: Antes de pedir la unidad de origen y destino, podrías mostrar una lista de las unidades disponibles para cada tipo de conversión.
  • Manejo de mayúsculas/minúsculas: Podrías convertir la entrada de unidades a minúsculas para hacer el programa más flexible.
  • Historial de conversiones: Podrías implementar un sistema para guardar las últimas conversiones realizadas.
  • Configuración persistente: Permitir al usuario establecer unidades preferidas que se recuerden entre ejecuciones.
  • Conclusión

    ¡Felicidades! Has creado un convertidor de unidades funcional en Go. Este proyecto te ha permitido practicar con interfaces, mapas, manejo de errores y más. Recuerda, la práctica constante es clave para dominar Go.

    ¿Qué otros proyectos te gustaría ver? ¿Tienes alguna pregunta sobre los conceptos que hemos cubierto? ¡Déjanos tus comentarios abajo!

    Comentarios

    Formulario de contacto

    Enviar