CAP 06 · LEC 01·Interfaces

Interfaces: implementación implícita y polimorfismo

En Go una interfaz es un conjunto de métodos. No se declara que un tipo la implementa: si el tipo tiene los métodos requeridos, ya la implementa. Esto se llama implementación implícita o duck typing estructural.

● INTERMEDIO8 min lecturapor Fernando Herrera · actualizado mayo de 2026
¿Encontraste un error o algo que mejorar?Editá esta lección en GitHub →

¿Qué es una interfaz en Go?

Una interfaz define un contrato: un conjunto de métodos que un tipo debe tener. A diferencia de Java o C#, en Go no existe la palabra clave implements. Si un tipo tiene todos los métodos de la interfaz, automáticamente la satisface.

package main import "fmt" // Definimos un contrato: cualquier tipo con Sound() string es un Animal type Animal interface { Sound() string } type Dog struct { Name string } // Dog tiene Sound() -> Dog satisface Animal automáticamente func (d Dog) Sound() string { return d.Name + " dice: Woof!" } type Cat struct { Name string } func (c Cat) Sound() string { return c.Name + " dice: Meow!" } func main() { var a Animal = Dog{Name: "Rex"} fmt.Println(a.Sound()) a = Cat{Name: "Luna"} fmt.Println(a.Sound()) }
SalidaRex dice: Woof! Luna dice: Meow!
Sin `implements`

Go nunca te obliga a declarar qué interfaces implementa un tipo. La relación se resuelve en tiempo de compilación mirando los métodos. Esto permite que un tipo satisfaga interfaces definidas en paquetes que ni siquiera importa.

Polimorfismo con interfaces

Una variable de tipo interfaz puede contener cualquier valor cuyo tipo satisfaga la interfaz. Es la forma idiomática de hacer polimorfismo en Go.

package main import "fmt" type Shape interface { Area() float64 } type Circle struct { Radius float64 } func (c Circle) Area() float64 { return 3.1416 * c.Radius * c.Radius } type Rectangle struct { Width, Height float64 } func (r Rectangle) Area() float64 { return r.Width * r.Height } // Una sola función trabaja con cualquier Shape func printArea(s Shape) { fmt.Printf("Área: %.2f\n", s.Area()) } func main() { shapes := []Shape{ Circle{Radius: 5}, Rectangle{Width: 4, Height: 3}, } for _, s := range shapes { printArea(s) } }
SalidaÁrea: 78.54 Área: 12.00

Valor de interfaz: (type, value)

Internamente, un valor de interfaz es un par (tipo concreto, valor). Esto explica por qué fmt.Println puede saber si imprimir un Dog o un Cat aunque la variable sea de tipo Animal.

package main import "fmt" type Animal interface { Sound() string } type Dog struct{ Name string } func (d Dog) Sound() string { return "Woof" } func main() { var a Animal = Dog{Name: "Rex"} // %T imprime el tipo concreto dentro de la interfaz // %v imprime el valor fmt.Printf("type=%T value=%v\n", a, a) fmt.Println(a.Sound()) }
Salidatype=main.Dog value={Rex} Woof

Nil interface vs nil concreto

Una interfaz solo es nil cuando tanto su tipo como su valor son nil. Si asignas un puntero nil concreto, la interfaz no es nil — tiene tipo pero no valor. Es una de las trampas más famosas de Go.

package main import "fmt" type Animal interface { Sound() string } type Dog struct{ Name string } func (d *Dog) Sound() string { if d == nil { return "(perro nulo)" } return d.Name } func newDog() *Dog { return nil // retornamos un *Dog nil } func main() { // Interfaz realmente nil var a1 Animal fmt.Println(a1 == nil) // true // Interfaz con tipo *Dog pero valor nil -> NO es nil var d *Dog = newDog() var a2 Animal = d fmt.Println(a2 == nil) // false (sorprendente) fmt.Printf("%T\n", a2) // *main.Dog }
Salidatrue false *main.Dog
Cuidado al retornar errores

Este patrón causa bugs frecuentes con error. Si retornas un *MyError nil desde una función que declara error como tipo de retorno, el caller verá un error no-nil. Retorna nil literal, no un puntero concreto nil.

Comparación con clases abstractas

En lenguajes OOP tradicionales, una clase abstracta declara un contrato y las subclases lo heredan con extends o implements. En Go no hay herencia ni declaración explícita.

ConceptoJava / PythonGo
Declarar contratoabstract class / ABCinterface
Implementar contratoimplements / herenciaTener los métodos
RelaciónNominal (por nombre)Estructural (por forma)
AcoplamientoAlto: el tipo conoce la interfazBajo: la interfaz puede ser ajena
Métodos por defectoNo (solo firmas)
Interfaces consumidas por el llamador

En Go la convención es definir la interfaz en el paquete que la consume, no en el que la implementa. Así el código que produce los valores no depende de la interfaz, y el código que los consume declara exactamente lo que necesita.