Interfaces
Las interfaces en Go son implicitas: un tipo las satisface automaticamente si implementa todos sus metodos. No hay implements ni extends. Esto habilita un polimorfismo flexible conocido como duck typing.
Interfaces Implicitas
Si camina como pato y hace quack como pato, es un pato:
type Forma interface {
Area() float64
}
type Circulo struct {
Radio float64
}
func (c Circulo) Area() float64 {
return math.Pi * c.Radio * c.Radio
}
type Rectangulo struct {
Ancho, Alto float64
}
func (r Rectangulo) Area() float64 {
return r.Ancho * r.Alto
}
func imprimirArea(f Forma) {
fmt.Printf("Area: %.2f\n", f.Area())
}
func main() {
imprimirArea(Circulo{Radio: 5}) // Area: 78.54
imprimirArea(Rectangulo{Ancho: 3, Alto: 4}) // Area: 12.00
}
Ni Circulo ni Rectangulo declaran que implementan Forma. Simplemente tienen el metodo Area() y eso basta.
La Interfaz Vacia: interface{} y any
interface{} (alias any desde Go 1.18) no tiene metodos, por lo que todo tipo la satisface:
func describir(v any) {
fmt.Printf("Valor: %v, Tipo: %T\n", v, v)
}
func main() {
describir(42) // Valor: 42, Tipo: int
describir("hola") // Valor: hola, Tipo: string
describir(true) // Valor: true, Tipo: bool
describir([]int{1}) // Valor: [1], Tipo: []int
}
Usa any con moderacion. Pierdes seguridad de tipos y necesitas type assertions para operar.
Interfaces Comunes: Stringer y error
fmt.Stringer
Controla como se imprime tu tipo:
type Moneda struct {
Cantidad float64
Divisa string
}
func (m Moneda) String() string {
return fmt.Sprintf("%.2f %s", m.Cantidad, m.Divisa)
}
func main() {
precio := Moneda{Cantidad: 49.99, Divisa: "USD"}
fmt.Println(precio) // 49.99 USD
}
error
La interfaz mas usada de Go:
type ErrorValidacion struct {
Campo string
Mensaje string
}
func (e *ErrorValidacion) Error() string {
return fmt.Sprintf("campo %s: %s", e.Campo, e.Mensaje)
}
func validarEdad(edad int) error {
if edad < 0 {
return &ErrorValidacion{
Campo: "edad",
Mensaje: "no puede ser negativa",
}
}
return nil
}
Type Assertion
Extrae el tipo concreto de una interfaz:
func procesar(v any) {
// Forma segura con ok
s, ok := v.(string)
if ok {
fmt.Println("Es string:", s)
return
}
n, ok := v.(int)
if ok {
fmt.Println("Es int:", n)
return
}
fmt.Println("Tipo desconocido")
}
func main() {
procesar("hola") // Es string: hola
procesar(42) // Es int: 42
procesar(3.14) // Tipo desconocido
}
Sin el segundo valor ok, una assertion fallida causa panic:
var v any = "texto"
n := v.(int) // PANIC: interface conversion error
Type Switch
Mas elegante que multiples type assertions:
func clasificar(v any) string {
switch val := v.(type) {
case int:
return fmt.Sprintf("entero: %d", val)
case string:
return fmt.Sprintf("texto: %q (len=%d)", val, len(val))
case bool:
if val {
return "verdadero"
}
return "falso"
case []int:
return fmt.Sprintf("slice de %d elementos", len(val))
default:
return fmt.Sprintf("tipo no manejado: %T", val)
}
}
func main() {
fmt.Println(clasificar(42)) // entero: 42
fmt.Println(clasificar("Go")) // texto: "Go" (len=2)
fmt.Println(clasificar([]int{1, 2})) // slice de 2 elementos
}
Composicion de Interfaces
Las interfaces se componen embebiendo otras interfaces:
type Lector interface {
Leer(p []byte) (int, error)
}
type Escritor interface {
Escribir(p []byte) (int, error)
}
type LectorEscritor interface {
Lector
Escritor
}
Esto es exactamente lo que hace la stdlib de Go:
// En el paquete io
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
type ReadWriter interface {
Reader
Writer
}
Interfaces Comunes de la Stdlib
| Interfaz | Paquete | Metodo | Uso |
|---|---|---|---|
Reader | io | Read([]byte) (int, error) | Leer datos |
Writer | io | Write([]byte) (int, error) | Escribir datos |
Closer | io | Close() error | Liberar recursos |
Stringer | fmt | String() string | Representacion textual |
error | builtin | Error() string | Manejo de errores |
Handler | net/http | ServeHTTP(w, r) | Manejar HTTP |
Interface | sort | Len, Less, Swap | Ordenar colecciones |
Ejemplo con sort.Interface
type Personas []Persona
type Persona struct {
Nombre string
Edad int
}
func (p Personas) Len() int { return len(p) }
func (p Personas) Less(i, j int) bool { return p[i].Edad < p[j].Edad }
func (p Personas) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func main() {
grupo := Personas{
{"Carlos", 30},
{"Ana", 25},
{"Luis", 35},
}
sort.Sort(grupo)
for _, p := range grupo {
fmt.Printf("%s (%d)\n", p.Nombre, p.Edad)
}
// Ana (25)
// Carlos (30)
// Luis (35)
}
Ejemplo con io.Reader
func contarPalabras(r io.Reader) (int, error) {
scanner := bufio.NewScanner(r)
scanner.Split(bufio.ScanWords)
count := 0
for scanner.Scan() {
count++
}
return count, scanner.Err()
}
func main() {
// Funciona con strings
r := strings.NewReader("hola mundo desde Go")
n, _ := contarPalabras(r)
fmt.Println(n) // 4
// Funciona con archivos, HTTP bodies, buffers...
// Cualquier cosa que implemente io.Reader
}
Patron: Accept Interfaces, Return Structs
Este es uno de los principios mas importantes de diseno en Go:
// BIEN: acepta interfaz, cualquier Reader funciona
func ProcesarDatos(r io.Reader) (*Resultado, error) {
data, err := io.ReadAll(r)
if err != nil {
return nil, err
}
return &Resultado{Datos: data}, nil
}
// MAL: acepta tipo concreto, limita la flexibilidad
func ProcesarDatos(f *os.File) (*Resultado, error) { ... }
Beneficios:
- Flexibilidad: quien llama decide la implementacion
- Testeable: puedes pasar mocks que cumplan la interfaz
- Desacoplado: no dependes de tipos concretos
Verificar Implementacion en Compilacion
Puedes forzar que un tipo implemente una interfaz:
type Almacen interface {
Guardar(datos []byte) error
Obtener(id string) ([]byte, error)
}
type AlmacenMemoria struct {
datos map[string][]byte
}
// Verificacion en tiempo de compilacion
var _ Almacen = (*AlmacenMemoria)(nil)
func (a *AlmacenMemoria) Guardar(datos []byte) error {
// implementacion...
return nil
}
func (a *AlmacenMemoria) Obtener(id string) ([]byte, error) {
// implementacion...
return nil, nil
}
La linea var _ Almacen = (*AlmacenMemoria)(nil) genera error de compilacion si AlmacenMemoria no satisface Almacen.
Resumen
| Concepto | Descripcion |
|---|---|
| Implicitas | No se declara implements |
any / interface{} | Acepta cualquier tipo |
| Type assertion | v.(T) extrae tipo concreto |
| Type switch | switch v.(type) para multiples tipos |
| Composicion | Interfaces embeben otras interfaces |
| Patron clave | Acepta interfaces, retorna structs |