Skip to content

Interfaces

En Go, una interfaz es un tipo abstracto que se utiliza para definir un conjunto de firmas de métodos sin proporcionar la implementación de los métodos. El concepto central de las interfaces es describir comportamiento, mientras que la implementación concreta del comportamiento la proporciona el tipo que implementa la interfaz. Las interfaces en Go se utilizan ampliamente para implementar polimorfismo, acoplamiento débil y reutilización de código.

Concepto

La historia del desarrollo de las interfaces en Go tiene un punto de inflexión. En Go 1.17 y versiones anteriores, la definición oficial de interfaz en el manual de referencia era: un conjunto de métodos.

An interface type specifies a method set called its interface.

La definición de implementación de interfaz era:

A variable of interface type can store a value of any type with a method set that is any superset of the interface. Such a type is said to implement the interface

Traducido significa que cuando el conjunto de métodos de un tipo es un superconjunto del conjunto de métodos de una interfaz, y el valor de ese tipo puede ser almacenado por una variable de tipo interfaz, entonces se dice que ese tipo implementa esa interfaz.

Sin embargo, en Go 1.18, la definición de interfaz cambió, definiéndose como: un conjunto de tipos.

An interface type defines a type set.

La definición de implementación de interfaz es:

A variable of interface type can store a value of any type that is in the type set of the interface. Such a type is said to implement the interface

Traducido significa que cuando un tipo está dentro del conjunto de tipos de una interfaz, y el valor de ese tipo puede ser almacenado por una variable de tipo interfaz, entonces se dice que ese tipo implementa esa interfaz. También se proporcionó la siguiente definición adicional:

Se puede decir que el tipo T implementa la interfaz I cuando:

  • T no es una interfaz y es un elemento del conjunto de tipos de la interfaz I
  • T es una interfaz y el conjunto de tipos de T es un subconjunto del conjunto de tipos de la interfaz I

Si T implementa una interfaz, entonces el valor de T también implementa esa interfaz.

El mayor cambio en Go 1.18 fue la adición de genéricos. La nueva definición de interfaz está diseñada para servir a los genéricos, pero no afecta en absoluto el uso anterior de las interfaces. Al mismo tiempo, las interfaces se dividieron en dos categorías:

  • Interfaz Básica (Basic Interface): Una interfaz que solo contiene un conjunto de métodos es una interfaz básica
  • Interfaz General (General Interface): Una interfaz que contiene un conjunto de tipos es una interfaz general

¿Qué es un conjunto de métodos? Un conjunto de métodos es un conjunto de métodos. De la misma manera, un conjunto de tipos es un conjunto de tipos.

TIP

Puede que pienses que este concepto es oscuro y difícil de entender, pero en realidad no necesitas entender todo lo anterior en absoluto.

Interfaz Básica

Como se mencionó anteriormente, una interfaz básica es un conjunto de métodos, es decir, un conjunto de métodos.

Declaración

Veamos primero cómo se ve una interfaz.

go
type Person interface {
  Say(string) string
  Walk(int)
}

Esta es una interfaz Person con dos métodos expuestos: Walk y Say. En una interfaz, los nombres de los parámetros de las funciones ya no son importantes, aunque también está permitido agregar nombres de parámetros y nombres de valores de retorno si se desea.

Inicialización

Una interfaz por sí sola no puede ser inicializada porque es solo un conjunto de especificaciones sin implementación concreta, pero puede ser declarada.

go
func main() {
   var person Person
   fmt.Println(person)
}

Salida

 <nil>

Implementación

Veamos un ejemplo primero. Una empresa de construcción quiere una grúa de especificaciones especiales, por lo que proporciona las especificaciones especiales y los planos de la grúa, y señala que la grúa debe tener las funciones de levantar y izar carga. La empresa de construcción no es responsable de fabricar la grúa, solo proporciona una especificación, esto se llama interfaz. Entonces la empresa A acepta el pedido, fabrica una grúa legendaria según su tecnología exclusiva y la entrega a la empresa de construcción. A la empresa de construcción no le importa qué tecnología se utiliza para implementarla, ni le importa qué grúa legendaria es, siempre que pueda levantar e izar carga, simplemente la usa como una grúa normal. Proporcionar funciones concretas según las especificaciones, esto se llama implementación. Usar funciones según las especificaciones de la interfaz, ocultando su implementación interna, esto se llama programación orientada a interfaces. Después de un tiempo, la grúa legendaria falló y la empresa A quebró. Entonces la empresa B fabricó una grúa aún más poderosa según las especificaciones. Como también tiene las funciones de levantar e izar carga, puede conectarse perfectamente con la grúa legendaria sin afectar el progreso de la construcción, y el edificio se completa con éxito. La implementación interna cambia pero la función no cambia, no afecta el uso anterior, puede ser reemplazada arbitrariamente, este es el beneficio de la programación orientada a interfaces.

A continuación se describe la situación anterior usando Go:

go
// Interfaz de grúa
type Crane interface {
  JackUp() string
  Hoist() string
}

// Grúa A
type CraneA struct {
  work int // Los campos internos diferentes representan detalles internos diferentes
}

func (c CraneA) Work() {
  fmt.Println("Usando tecnología A")
}
func (c CraneA) JackUp() string {
  c.Work()
  return "jackup"
}

func (c CraneA) Hoist() string {
  c.Work()
  return "hoist"
}

// Grúa B
type CraneB struct {
  boot string
}

func (c CraneB) Boot() {
  fmt.Println("Usando tecnología B")
}

func (c CraneB) JackUp() string {
  c.Boot()
  return "jackup"
}

func (c CraneB) Hoist() string {
  c.Boot()
  return "hoist"
}

type ConstructionCompany struct {
  Crane Crane // Solo almacena grúas según el tipo Crane
}

func (c *ConstructionCompany) Build() {
  fmt.Println(c.Crane.JackUp())
  fmt.Println(c.Crane.Hoist())
  fmt.Println("Construcción completada")
}

func main() {
  // Usando grúa A
  company := ConstructionCompany{CraneA{}}
  company.Build()
  fmt.Println()
  // Cambiando a grúa B
  company.Crane = CraneB{}
  company.Build()
}

Salida

Usando tecnología A
jackup
Usando tecnología A
hoist
Construcción completada

Usando tecnología B
jackup
Usando tecnología B
hoist
Construcción completada

En el ejemplo anterior, se puede observar que la implementación de la interfaz es implícita, lo que corresponde a la definición oficial de implementación de interfaz básica: el conjunto de métodos es un superconjunto del conjunto de métodos de la interfaz. Por lo tanto, en Go, para implementar una interfaz no se necesita la palabra clave implements para especificar explícitamente qué interfaz implementar. Siempre que se implementen todos los métodos de una interfaz, eso significa que se ha implementado esa interfaz. Una vez que hay una implementación, se puede inicializar la interfaz. La estructura de la empresa de construcción declara una variable miembro de tipo Crane, que puede almacenar todos los valores que implementan la interfaz Crane. Como es una variable de tipo Crane, los únicos métodos a los que se puede acceder son JackUp y Hoist, otros métodos internos como Work y Boot no son accesibles.

Como se mencionó anteriormente, cualquier tipo personalizado puede tener métodos, entonces según la definición de implementación, cualquier tipo personalizado puede implementar una interfaz. A continuación se presentan algunos ejemplos especiales.

go
type Person interface {
   Say(string) string
   Walk(int)
}

type Man interface {
   Exercise()
   Person
}

El conjunto de métodos de la interfaz Man es un superconjunto de Person, por lo que Man también implementa la interfaz Person, aunque esto se parece más a una "herencia".

go
type Number int

func (n Number) Say(s string) string {
  return "bibibibibi"
}

func (n Number) Walk(i int) {
  fmt.Println("no se puede caminar")
}

El tipo subyacente de Number es int. Aunque esto parece absurdo en otros lenguajes, el conjunto de métodos de Number es de hecho un superconjunto de Person, por lo que también cuenta como implementación.

go
type Func func()

func (f Func) Say(s string) string {
  f()
  return "bibibibibi"
}

func (f Func) Walk(i int) {
  f()
  fmt.Println("no se puede caminar")
}

func main() {
  var function Func
  function = func() {
    fmt.Println("hacer algo")
  }
  function()
}

De la misma manera, los tipos de función también pueden implementar interfaces.

Interfaz Vacía

go
type Any interface{

}

La interfaz Any no tiene conjunto de métodos internos. Según la definición de implementación, todos los tipos son implementaciones de la interfaz Any, porque el conjunto de métodos de todos los tipos es un superconjunto del conjunto vacío. Por lo tanto, la interfaz Any puede almacenar valores de cualquier tipo.

go
func main() {
  var anything Any

  anything = 1
  println(anything)
  fmt.Println(anything)

  anything = "algo"
  println(anything)
  fmt.Println(anything)

  anything = complex(1, 2)
  println(anything)
  fmt.Println(anything)

  anything = 1.2
  println(anything)
  fmt.Println(anything)

  anything = []int{}
  println(anything)
  fmt.Println(anything)

  anything = map[string]int{}
  println(anything)
  fmt.Println(anything)
}

Salida

(0xe63580,0xeb8b08)
1
(0xe63d80,0xeb8c48)
algo
(0xe62ac0,0xeb8c58)
(1+2i)
(0xe62e00,0xeb8b00)
1.2
(0xe61a00,0xc0000080d8)
[]
(0xe69720,0xc00007a7b0)
map[]

Al observar la salida, se encontrará que los resultados de las dos salidas son inconsistentes. En realidad, una interfaz interna se puede considerar como una tupla compuesta por (val, type), donde type es el tipo concreto. Al llamar a un método, se llamará al valor concreto del tipo concreto.

go
interface{}

Esta también es una interfaz vacía, pero es una interfaz vacía anónima. Durante el desarrollo, generalmente se usa una interfaz vacía anónima para indicar que se acepta un valor de cualquier tipo, como en el siguiente ejemplo:

go
func main() {
   DoSomething(map[int]string{})
}

func DoSomething(anything interface{}) interface{} {
   return anything
}

En actualizaciones posteriores, los oficiales propusieron otra solución. Por conveniencia, se puede usar any para reemplazar interface{}. Los dos son completamente equivalentes porque el primero es solo un alias de tipo, como sigue:

go
type any = interface{}

Al comparar interfaces vacías, se compara su tipo subyacente. Si los tipos no coinciden, el resultado es false. Luego se comparan los valores, por ejemplo:

go
func main() {
  var a interface{}
  var b interface{}
  a = 1
  b = "1"
  fmt.Println(a == b)
  a = 1
  b = 1
  fmt.Println(a == b)
}

La salida es:

false
true

Si el tipo subyacente no es comparable, ocurrirá un panic. Para Go, la comparabilidad de los tipos de datos incorporados es la siguiente:

TipoComparableCriterio
Tipos numéricosSi los valores son iguales
Tipo cadenaSi los valores son iguales
Tipo arraySi todos los elementos del array son iguales
Tipo sliceNoNo comparable
StructSi todos los valores de los campos son iguales
Tipo mapNoNo comparable
CanalSi las direcciones son iguales
PunteroSi las direcciones almacenadas en los punteros son iguales
InterfazSi los datos almacenados en el nivel subyacente son iguales

En Go hay un tipo de interfaz dedicado para representar todos los tipos comparables, es decir comparable:

go
type comparable interface{ comparable }

TIP

Si intentas comparar tipos no comparables, ocurrirá un panic.

Interfaz General

Las interfaces generales están diseñadas para servir a los genéricos. Una vez que dominas los genéricos, dominas las interfaces generales. Por favor, consulta Genéricos para más información.

Golang editado por www.golangdev.cn