Las interfaces en Go funcionan de forma distinta a la mayoría de lenguajes orientados a objetos: no hace falta declarar que un tipo implementa una interfaz. Si el tipo tiene los métodos correctos, la cumple automáticamente. A esto se le llama satisfacción implícita o duck typing estático, y es una de las decisiones de diseño más elogiadas del lenguaje.
Definir e implementar una interfaz
package main
import (
"fmt"
"math"
)
type Forma interface {
Area() float64
Perimetro() float64
}
type Circulo struct{ Radio float64 }
type Rectangulo struct{ Ancho, Alto float64 }
func (c Circulo) Area() float64 { return math.Pi * c.Radio * c.Radio }
func (c Circulo) Perimetro() float64 { return 2 * math.Pi * c.Radio }
func (r Rectangulo) Area() float64 { return r.Ancho * r.Alto }
func (r Rectangulo) Perimetro() float64 { return 2 * (r.Ancho + r.Alto) }
func describir(f Forma) {
fmt.Printf("Área: %.2f, Perímetro: %.2fn", f.Area(), f.Perimetro())
}
func main() {
describir(Circulo{Radio: 5})
describir(Rectangulo{Ancho: 4, Alto: 3})
}
Ninguno de los dos tipos menciona la interfaz Forma en su definición. El compilador verifica que ambos tienen los métodos necesarios y permite pasar cualquiera de ellos donde se espere un Forma.
Composición de interfaces
Las interfaces pueden incluir otras interfaces. Esto permite construir contratos más amplios a partir de contratos pequeños sin duplicar métodos:
type Lector interface {
Leer(p []byte) (n int, err error)
}
type Escritor interface {
Escribir(p []byte) (n int, err error)
}
type LectorEscritor interface {
Lector
Escritor
}
La interfaz vacía: interface{} y any
La interfaz sin métodos la cumple cualquier tipo. Desde Go 1.18 tiene el alias any:
func imprimir(v any) {
fmt.Printf("tipo: %T, valor: %vn", v, v)
}
func main() {
imprimir(42)
imprimir("hola")
imprimir([]int{1, 2, 3})
}
Úsala con moderación: perder el tipo significa que el compilador ya no puede ayudarte. Prefiere siempre interfaces con métodos concretos.
Type assertion
Cuando tienes un valor de tipo interfaz y necesitas el tipo concreto, usa una type assertion:
var v any = "programacion.net"
s, ok := v.(string)
if ok {
fmt.Println(strings.ToUpper(s)) // PROGRAMACION.NET
}
// sin ok, un tipo incorrecto produce un pánico:
// n := v.(int) // panic: interface conversion: string is not int
Type switch
Para manejar varios tipos posibles, el type switch es más idiomático que encadenar type assertions:
func describeTipo(v any) string {
switch t := v.(type) {
case int:
return fmt.Sprintf("entero: %d", t)
case string:
return fmt.Sprintf("cadena de %d bytes", len(t))
case bool:
if t {
return "verdadero"
}
return "falso"
case []int:
return fmt.Sprintf("slice de %d enteros", len(t))
default:
return fmt.Sprintf("tipo desconocido: %T", t)
}
}
func main() {
fmt.Println(describeTipo(42))
fmt.Println(describeTipo("hola"))
fmt.Println(describeTipo([]int{1, 2}))
}
Interfaces de un solo método: el convenio de nombre
Por convenio, las interfaces con un único método se nombran añadiendo el sufijo -er al nombre del método: Reader, Writer, Stringer, Closer. Seguir este convenio hace tu código más fácil de entender para cualquier programador Go.
