Fork me on GitHub

klashxx    Archive    About    Talks    Feed

Interfaces en Go

Un interface en Go es un tipo, tan simple como eso.

Parce una tontería pero una vez que tenemos claro esto lo demás viene rodado.

Eso si es un tipo especial, especial porque solo puede contener métodos, es decir, las acciones que se podrán ejecutar sobre nuestros tipos, generalmente structs.

Para empezar supongamos un objeto Bici sobre el que definimos un método para establecer la marcha que vamos a usar:

package main

type Bici struct {
	marcha int
}

func (b *Bici) PonMarcha(marcha int) {
	b.marcha = marcha
}

func main() {
	vehiculo := &Bici{}
	vehiculo.PonMarcha(14)
}

:warning: NOTA: PonMarcha debe implementarse con un pointer receiver para permitir la modificación de los valores / propiedades de la estructura.1

¡Vale fácil! Ahora añadimos una Moto:

package main

type Vehiculo interface {
	PonMarcha(marcha int)
}

type Bici struct {
	marcha int
}

func (b *Bici) PonMarcha(marcha int) {
	b.marcha = marcha
}

type Moto struct {
	marcha int
}

func (m *Moto) PonMarcha(marcha int) {
	m.marcha = marcha
}

func main() {
	vehiculo := &Bici{}
	vehiculo.PonMarcha(14)
	vehiculo = &Moto{}
	vehiculo.PonMarcha(2)
}

Parece directo… pero este código no es compilable:

./main.go:29: cannot use Moto literal (type *Moto) as type *Bici in assignment

Moto y Bici no tienen nada en común … ¿o sí?

Parece evidente que estos dos objetos comparten ciertas acciones / métodos, en nuestro caso PonMarcha, por lo que podríamos definir un tipo interface que los agrupe:

type Vehiculo interface {
	PonMarcha(marcha int)
}

Veamos el código completo:

package main

type Bici struct {
	marcha int
}

func (b *Bici) PonMarcha(marcha int) {
	b.marcha = marcha
}

type Moto struct {
	marcha int
}

func (m *Moto) PonMarcha(marcha int) {
	m.marcha = marcha
}

type Vehiculo interface {
	PonMarcha(marcha int)
}

func main() {
        var vehiculo Vehiculo

	vehiculo = &Bici{}
	vehiculo.PonMarcha(14)
	vehiculo = &Moto{}
	vehiculo.PonMarcha(2)
}

¡Nuestro código ya compila! Podemos usar una variable tipo Vehiculo porque sabemos que tanto el tipo Bici como el tipo Moto satisfacen su interface.

Esa es la condición sin ecua non: todos los tipos deben implementar todos los métodos del interface, pero no quedan limitados por el, por ejemplo a Moto se le podría añadir la acción para echar gasolina:

func (m *Moto) EchaGasolina(nivel int) {
    m.nivel = nivel
}

Seguiría satisfaciendo el interface Vehiculo pero , OJO, como es lógico, no se podría acceder al nuevo método desde el:

./main.go:36: vehiculo.EchaGasolina undefined (type Vehiculo has no field or method EchaGasolina)

Stringer

Este es el ejemplo clásico del interface Stringer que nos acercará mucho más a su uso más práctico.

Está definido en el paquete fmt y su código no puede ser más simple:

type Stringer interface {
        String() string
}

Todo lo que nos importa es: si definimos un objeto que satisfaga este interface podremos aprovecharnos la funcionalidad implementada en print definida como:

The String method is used to print values passed as an operand to any format that accepts a string or to an unformatted printer such as Print.

Un tipo que implemente el interface Stringer podrá pasarse a cualquier función de impresión de fmt para obtener su representación.

Veámoslo en nuestro ejemplo:

package main

import (
  "fmt"
  "strconv"
  )

type Bici struct {
    marcha int
}

func (b *Bici) PonMarcha(marcha int) {
    b.marcha = marcha
}

func (b Bici) String() string {
    return "Bici en marcha número: "+strconv.Itoa(b.marcha)
}

type Moto struct {
    marcha int
    nivel int
}

func (m *Moto) PonMarcha(marcha int) {
    m.marcha = marcha
}

func (m Moto) String() string {
    return "Moto en marcha número: "+strconv.Itoa(m.marcha)
}

type Vehiculo interface {
    PonMarcha(marcha int)
    String() string
}

func main() {
    var vehiculo Vehiculo

    vehiculo = &Bici{}
    vehiculo.PonMarcha(14)
    fmt.Println("Vehículo: ", vehiculo)

    vehiculo = &Moto{}
    vehiculo.PonMarcha(2)
    fmt.Println("Vehículo: ", vehiculo)
}

Nos devuelve:

Vehículo:  Bici en marcha número: 14
Vehículo:  Moto en marcha número: 2

Interface{}

Es el interface vacio, no implementa ningún método y por lo tanto es satisfecho por cualquier tipo.

En base a este tipo es posible implementar una función que reciba cualquier tipo de valor, por ejemplo fmt.Print que tiene la siguiente signature:

func Print(a ...interface{}) (n int, err error)

:warning: Nota: el tipo precedido por ... indica que esta función es variadic por lo que puede ser invocada con cero o mas argumentos para es parámetro.

¿Cuál sería el tipo de a?

El tipo de a no es cualquier tipo sino interface{}, al pasar un valor como argumento go lo convertirá (si es necesario) al tipo interface{}.

Un interface^ se construye con dos palabras de dirección de memoria:

  • Una se usa para apuntar la tabla de metodos asociada al tipo “original”.
  • La otra para apuntar a los datos.

Entender representación evita bastantes confusiones.

Ahora supongamos que queremos construir un slice de tipos Vehiculo:

vehiculos := []Vehiculo{&Bici{}, &Moto{}}

Podemos añadirle directamente los tipos que lo satisfacen porque la conversión al tipo Vehiculo se lleva a cabo directamente.

En el slice vehiculos todos sus elementos son tipo Vehiculo pero, cada uno de sus valores tienen diferentes tipos de origen.

Si definimos Vehiculo como un interface vacio:

type Vehiculo interface{}

Podremos realizar la asignación, porque tanto Bicicomo Moto sadisfacen el interface vacio:

vehiculos := []Vehiculo{&Bici{}, &Moto{}}

Pero no tendremos acceso a ninguno de sus métodos.

  1. ¿Cuándo usar un pointer receiver y otro estándar? Un receiver puede considerarse como un argumente que se pasa a un método.

    • Si queremos modificar el receiver usaremos un pointer y le pasamos la referencia.
    • Si la estructura atachada es muy grande por lo que pasarla por valor es caro.
    • Por consistencia, si la mayor parte de los receiver ya son tipo pointer.

comments powered by Disqus
© Juan Diego Godoy Robles